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).
|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?|
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.
Option two: Integration via iFrames
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.
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|
|Bootstrapping several SPAs||~¹||X||X||X||X|
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!