Angular in a microservice world

How to marry SPAs to Microservices
27
Apr

Angular in a microservice world

Microservices and Single Page Applications (SPAs) are currently two of the most important trends in architecture. What's the best way to combine them? The problem is still rather young, so there is no general solution available yet. This article discusses different approaches alongside their advantages and disadvantages, thus enabling you to make an educated choice.

Microservices are currently very popular, as are single page applications. How to best use these two approaches together has been something I’ve been discussing with my clients over the past few months. The exciting thing is that there is no clear answer. Oftentimes, it depends strongly on the priorities and therefore also on the reasons why the decision for Microservices was made. Therefore, I will present some possibilities and evaluate them with regard to selected architectural goals you want to achieve with Microservices (Table 1).

architecture target Description
Isolation to other SPAs To what extent is the Angular application isolated from other single page applications running in the browser? Can individual applications influence each other?
Separate Development Can individual single page applications be developed separately?
Separate Deployment Can individual single page applications be deployed separately?
Different Frameworks Can different single page applications be written with different frameworks in different versions?
Single Page Shell Does the shell, which allows navigation between the individual single page applications, also present itself as an SPA or does the solution behave like a classic web application that loads individual pages and cannot store states locally?
Optimized Bundle Sizes Can bundle sizes be optimized by removing unnecessary framework components and redundancies across the individual micro apps?
Table 1: Architectural goals for SPA in the microservices environment

 

Option one: Integration via hyperlinks

The simplest way to integrate different clients into a microservices architecture is probably the use of hyperlinks. This way, the individual clients reference each other. In addition, higher-level shell applications can call the clients. While these clients are SPAs, this does not apply to the overall solution. Switching pages during the transition between clients means that the current SPA is terminated and its states are also lost. However, it is precisely the possibility of being able to maintain states in the client that leads to increased user-friendliness with SPAs. With classic web applications, states are unpopular if used in the web layer. To compensate for this disadvantage, you could back up the client-side states regularly. Browser databases such as IndexedDB are ideal for this purpose. Alternatively, the states can also be sent to the server to persist them in a database. This approach has the advantage that the user can continue exactly where he left off at a later point in time.

Saving stuff is easier said than done, though. Where do you get the states and how do you ensure that all states are considered? An answer to this question is provided by the Redux pattern that is popular in the SPA world. The redux pattern manages the entire application state by the store (Fig. 1). This store can be thought of as an in-memory database, which stores the application state in an object tree. In this metaphor, the nodes on the first level to some degree correspond to individual tables or collections, to use a word from the NoSQL environment. The application can read all nodes or be informed about changes via publish/subscribe. To prevent each component from writing randomly to the tree, however, direct write access is not provided. Instead, components send corresponding actions to the store, which forwards them to so-called reducers. These are functions that modify the state tree in a well-defined way. Since the application state is in central storage here, it can easily be persisted and restored later. If you want to do this with Angular, take a look at the @ngrx/store library. The development of this library was initiated by a member of the Angular core team; therefore it integrates very well with Angular. This is the de facto standard implementation in this area.

Fig. 1: The store in Redux (@ngrx/store) manages a central state

 

Option two: Integration via iFrames

“Really? iFrames?” This is the first reaction I get when I present this solution. I get that because iFrames have always been used for hacks. Their big siblings, the framesets introduced by Netscape in the 90s, have disappeared in the meantime from the HTML standard after being regarded as an antipattern for years. But if you think about the properties of iFrames, you can see that they are well suited for the project outlined here. As with hyperlink integration, for example, they offer perfect isolation between individual clients. Thus, different frameworks in different versions can be used without getting in each other’s way. As the evaluation at the end shows, iFrames even meet all the requirements set here. For example, states can be retained if the application loads each client into its own iFrame and displays or hides it as required. There is also a standardized JavaScript API for communication between iFrames. Of course, iFrames also has disadvantages: Graphical elements cannot project over iFrames. It also makes sense for the iFrame to be the same size as its content, so that the user does not have to scroll within the iFrame. The good news: The latter can be solved with JavaScript. And since others have already had this problem, you can also find libraries.

Option three: Bootstrapping several SPAs

By loading individual SPAs into your index.html can compensate for the disadvantages of iFrames. Since each client is loaded into the same browser window, clients share the same global namespace. This is problematic if frameworks are based on global variables, which is the case with jQuery, for example. Nowadays, this is rarely used for new single page applications, but lies dormant in the background of many component libraries. Libraries that change global browser objects are just as problematic. This includes the.js zone used by Angular. Another challenge is separating the Styles of the individual Micro Apps. In any way, you have to make sure that the Style Sheets of one micro app do not affect the styling of another one. Leveraging Shadow DOM, one of the core concepts associated with Web Components, can help here.

Even tough loading different SPAs dynamically and switching between them is just a matter of some lines of VanillaJS, you can also leverage meta frameworks for this. One example for this is Single SPA.

 

Option four: Web Components

This is more or less the same as bootstrapping several SPAs. But here each Micro App is published as a Web Component and hence following a standard. As Web Components are handled like ordinary html elements, you can use attributes and events for communication. Starting with version 6, Angular will support compiling down Angular Components to Web Components. As the resulting code is very close to the DOM, this affects bundle sizes in a positive way. As an solution or when using frameworks without Web Components support, you can mix this approach with option three ones mentioned above.

 

Option five: Plug-in architectures

Angular allows the dynamic loading of modules. The easiest way to do this is via the router, which uses this option for lazy loading. However, the low-level APIs below can also be used directly to load modules and dynamically create components. Things get exciting if you want to compile and deploy the individual modules separately, because this is not supported by webpack and the Angular CLI based on it. Although these two technologies allow referencing other files with require and import, they assume that everything is compiled together. So you have to use a different toolchain for this approach. The combination of SystemJS and Gulp is ideal for this purpose. A widespread seed project can be found on GitHub – angular-seed. A proof of concept for the idea outlined here can be found here. While plug-in architectures allow both, separate deployment and seamless presentation of all loaded plug-ins on a page, they have one drawback: It is assumed that for all modules, compatible versions of Angular are used.

 

Option six: The Majestic Monolith

My last suggestion is to create a front-end monolith. At first sight, this doesn’t fit into a microservice-oriented world but when you think carefully about its structure, you can reach some of the goals outlined at the beginning of the article with monoliths too. And this is what matters, right? Such a monolith must be well structured – or in David Heinemeier Hansson’s words, it has to be majestic.

One way to achieve this, is by using npm packages for each individual client. Thus, each client can be implemented and tested separately. To make the npm packages available to other teams, they can be distributed via an npm registry. This can also be a solution such as Nexus or Artifactory. If such a solution does not yet exist in your environment, the extremely lightweight implementation verdaccio is a good choice for a quick luck.

The generation of npm packages is not yet supported by the Angular CLI, but there are plans for the upcoming version. However, alternatives exist that conjure up the basic framework for such a project in an instant. Speaking from experience, the library ng-packagr is a good solution. The disadvantage of this solution isthat the npm packages must be integrated into a shell application before delivery. This is to be compiled together with all packages, and then everything is delivered together. A separate deployment is therefore not possible. However, compiling together makes optimizations like tree shaking possible.

Another disadvantage of using npm packages is that you might end up with too many versions and hence version conflicts. Also, when sub structuring one project, it is annoying to publish and reinstall libraries all the time. This issue is solved with the (project-) monorepo approach. It defines a repository with different projects that form a software system. You can compare this with Workspaces in Eclipse or Solutions in Visual Studio. A well-known tool to easily achieve this structure is Nx from Nrwl, a company founded by some Angular core team members. It extends the Angular CLI and scaffolds a workspace for your monorepo. Also, when a library within such a work space becomes interesting for others, you can easily extract it as an npm package using a tool like ng-packagr.

 

Microarchitecture

After choosing on of those approaches, you have to think about sub-structuring the individual micro apps. This is what I’m calling microarchitecture. The good news is that you already know your options because they overlap with the approaches we’ve talked about: npm packages, monorepos and Web Components.

 

Rating: It depends

Table 2 sums up how the discussed approaches help achieve the goals we’ve talked about in the beginning. However, there is no perfect solution. In fact, it depends on how important the architectural goals listed here are. For example, I have seen many successful projects that rely on npm packages. Especially when separate deployment is not important and Angular is widely used, this straightforward approach is well suited for developing individual clients separately.

Isolation to other SPA Separate Development Separate Deployment Different Frameworks Single Page Shell Optimized Bundle Sizes
Links X X X X
iFrames X X X X X
Bootstrapping several SPAs X X X X
Microservices X X X X
Plug-in-Architecture X X X
Majestic Monolith X X X

¹: Shadow DOM helps to isolate styles. On the process and JavaScript level you have to make sure yourself that the micro apps don’t affect each other in an unwanted way.

Table 2: Evaluation of the presented approaches on the basis of selected architectural goals

 

Conclusion

There is no perfect solution for implementing clients within the framework of microservices architectures. However, there are some solutions that have their own advantages and disadvantages. Therefore, it is necessary to evaluate these against the background of the prevailing architectural goals and to find strategies to compensate for negative consequences.

 

Free: download 40+ pages of JS wisdom


Extend your knowledge and improve your JS skills with iJS dossier!

40+ pages of deep insights into the world of JavaScript, TypeScript, node.js, React and more!

Get dossier for free now!

 

STAY TUNED!

 

BEHIND THE TRACKS OF iJS

Angular

Best-Practises with Angular

Vue.js

One of the most famous frameworks of modern days

JavaScript Practices & Tools

DevOps, Testing, Performance, Toolchain & SEO

Node.js

All about Node.js

React

From Basic concepts to unidirectional data flows