Angular in a Microservices Architecture
03/02/2021
During my time in the Brazilian National Electric System Operator program, I was part of the SAAT (Administration and Transmission Calculation System) project, which consists of a microservices architecture, with a .NET Core application for each use case, which in turn also has a specific Angular app.
This configuration shows that a logic is needed so that each use case focuses on its requirements and that common services are shared, giving us a great system working with a lot of code reuse, performance and maintainability
Structure
The first challenge is to abstract all the generic part that belongs to each Frontend. And when it comes to Angular, it is important to think about which modules each application has in common.
Thus, the solution turns out to be an adaptation of a very common pattern in Angular, using Core, Shared and Feature modules, which are, in short:
- Core Module would be responsible for containing the import of all the application’s common injectable services;
- Shared Module, which contains the import of all modules and files essential for the functioning of a Feature Module;
- Feature Module offers a set of functionality focused on a specific system requirement;
- And so, the Root Module, which is by default named as app.module.ts, is responsible for uniting all the features and services in common.
However, as the SAAT environment involves multiple applications, each Feature will be like a Root Module. And the shared module will now be in an NPM package of an Angular library, which will be a dependency of each use case (figure 2). As the each applications are independent, we do not need the idea of a Core Module.
The Shared Module (called in the project as SAAT Module) is responsible for all the export of local and global modules that are fundamental for the operation of applications, such as: BrowserAnimationsModule, ReactiveFormsModule and HttpClientModule, HTTP Interception module (which is SAAT’s own) and others (figure 3).
Local Modules
Among the applications, there are several behaviors that are common to all, such as intercepting requests, for error handling, setting the loading status and handling the authorization token. Thus, all this logic is implemented inside the library, and each application is only responsible for making some imports so that they maintain the same behavior and have unique and centralized important services.
For example, the SAATInterceptorModule, which adds two HTTP interception features from different services. In SAAT there is an Interceptor responsible for the traditional addition of the JWT (JSON WEB TOKEN) to the request header, and another one to handle errors and the HTTP requests status.
At Figure 5, a demonstration of how one of the interceptors works, the one for handling errors and managing the loading screen. The HttpStatusService service can be accessed by any use case so that it can show or hide the loading screen.
The requests made by each Angular application will go through the same intercept services, and thus, the responsibility of each use case is more focused only on its requirements.
Library Integration
In Figure 6, an example (with fake names) of how to integrate the library with an application of one of the use cases. The module is visibly cleaner and only handles files that are specific to the use case.
To complete the integration, it is also necessary for each application to protect the routing with a service that implements the canActivate permission checking method. In it, the JWT verification service is accessed and it is defined whether the user is authenticated or not.
Dependency Injection
In the process of centralizing services and components, a problem arises: the JWT intercept service needs endpoint information to make requests and redirects. And as the system has multiple environments: production, staging and development, the service needs a reference to the environment variable of the Angular application that is running.
The solution lies in using the Dependency Injection (DI) principle. Angular has its own dependency injection framework, and with the use of injectable tokens, it is possible to pass the environment variable to the library, and thus, inject it into any service or component that needs its properties (figure 9).
The environment variable reference is passed by the forRoot method and is registered as a library provider (Figure 6 and 8).
And so, the library retains great flexibility and also has not only services, as shown in the examples here, but also components, directives, auxiliary functions and among other functionalities that are reusable for each use case.
Diagrams in this article were made using the draw.io tool.