Angular Libraries and Microservices
Check out how to implement microservices on the front-end with client-side UI patterns and Angular libraries.
Join the DZone community and get the full member experience.
Join For FreeThis article is featured in the new DZone Guide to Microservices. Get your free copy for insightful articles, industry stats, and more!
We live in a microservices world, and this world is here to stay. Back-end developers need to dive into Domain-Driven Design, write stateless, resilient, highly available services, keep data synchronized through Change Data Capture, handle network failure, deal with authentication, authorization, JWT... and expose a beautiful Hypermedia API so the front-end developers can add a great user interface to it.
Good! But what about the front-end?
Let's say we have several microservices, several teams, several APIs and, therefore, several user interfaces. How can you create an integrated and consistent user interface so your users don't actually see that there are as many user interfaces as there are Microservices? In this post, I'm implementing the client-side UI composition design pattern using Angular Libraries.
A Microservices Architecture
Let's say we have a CD BookStore application that allows customers to buy CDs and books online, as well as administrators to check the inventory. We also have a complex ISBN number generator to deal with. If we model this application into microservices, we might end up splitting it into 3 separate microservices:
Store: Deals with displaying information on CDs and books, allows users to buy them.
Inventory: Allows administrators to check the availability of an item in the warehouse and buy new items if needed.
Generator: Generates ISBN numbers each time a new book is created.
We end up with two roles (user and admin) interacting with these 3 APIs through a single user interface:
Client-side UI composition.
In a perfect world, these three microservices are independent, developed by three different teams and, therefore, have three different user interfaces, each released at its own pace. On one hand, we have three different UIs, and on the other, we want our users to navigate into what they see as a single and integrated application. There are several ways of doing it, and one way I'm describing here is called the Client-side UI composition design pattern:
Problem: How to implement a UI screen or page that displays data from multiple services?
Solution: Each team develops a client-side UI component, such an Angular component, that implements the region of the page/screen for their service. A UI team is responsible for implementing the page skeletons that build pages/screens by composing multiple, service-specific UI components. The idea is to aggregate, on the client side, our three user interfaces into a single one and end up with something that looks like this:
Aggregating several UIs into a single one works better if they use compatible technologies, of course. In this example, I am using Angular and only Angular to create the 3 user-interfaces and the page skeleton. Of course, Web Components would be a perfect fit for this use case, but that's not the purpose of this article.
Angular Libraries
One novelty with Angular CLI 6 is the ability to easily create libraries. Coming back to our architecture, we could say the page skeleton is the Angular application (the CDBookStore application), and then, each microservice user interface is a library (Store, Inventory, Generator).
In terms of Angular CLI commands, this is what we have:
# Main app called CDBookStore
$ ng new cdbookstore --directory cdbookstore
/
-routing
true
# The 3 libraries for our 3 microservices UI
$ ng generate library store --prefix str
$ ng generate library inventory --prefix inv
$ ng generate library generator --prefix gen
Once you've executed these commands, you will get the following directory structure:
src/app/
is the good old Angular structure. In this directory, we will have the page skeleton of the CDBookStore application. As you will see, this skeleton page is only a sidebar to navigate from one Microservice UI to another.projects/
is the directory where all the libraries end upprojects/generator
: the UI for the Generator microservice.projects/inventory
: the UI for the Inventory microservice.projects/store
: the UI for the Store microservice.
The Skeleton Page
The skeleton page is there to aggregate all the other components. Basically, it's only a sidebar menu (allowing us to navigate from one Microservice UI to another) and a <router-outlet>
. It is where you could have login/logout and other user preferences. It looks like this:
In terms of code, despite using Bootstrap, there is nothing special. What's interesting is the way the routes are defined. The AppRouting Module only defines the routes of the main application (here, the Home and About menus).
const routes: Routes = [
{path: '', component: HomeComponent},
{path: 'home', component: HomeComponent},
{path: 'about', component: AboutComponent},
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
In the HTML side of the side menu bar, we can find the navigation directive and the router:
<ul class="list-unstyled components">
<li>
<a [routerLink]="['/home']">
<i class="fas fa-home"></i>
Home
</a>
<a [routerLink]="['/str']">
<i class="fas fa-store"></i>
Store
</a>
<a [routerLink]="['/inv']">
<i class="fas fa-chart-bar"></i>
Inventory
</a>
<a [routerLink]="['/gen']">
<i class="fas fa-cogs"></i>
Generator
</a>
</li>
</ul>
<!-- Page Content -->
<div id="content">
<router-outlet></router-outlet>
</div>
As you can see in the code above, the routerLink directive is used to navigate to the main app components as well as library components. The trick here is that /home
is defined in the routing module, but not /str
, /inv
or /gen
. These routes are defined in the libraries themselves.
Disclaimer: I am a terrible web designer/developer. If someone reading this post knows about jQuery and Angular and want to give me a hand, I would love to have the sidebar be collapsible. I even created an issue for you!
The Libraries
Now when we click on Store, Inventory, or Generator, we want the router to navigate to the library's components:
Notice the parent/child relationship between routers. In the image above, the red router-outlet is the parent and belongs to the main app. The green router-outlet is the child and belongs to the library. Notice that in the store-routing.module.ts, we use str as the main path, and all the other paths are children (using the children keyword):
const routes: Routes = [
{
path: 'str', component: StoreComponent,
children:
[
{path: '', component: HomeComponent},
{path: 'home', component: HomeComponent},
{path: 'book-list', component:
BookListComponent},
{path: 'book-detail', component:
BookDetailComponent},
]
},
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class StoreRoutingModule {
}
The beauty of having a parent/child router relationship is that you can benefit from "feature" navigation. This means that if you navigate to /home
or /about
, you stay in the main application, but if you navigate to /str
, /str/home
, /str/booklist
or /str/book-detail
, you navigate to the Store library. You can even do lazy loading on a per feature based (only load the Store library if needed).
The Store library has a navigation bar at the top, therefore, it needs the routerLink directive. Notice that we only need to use the child path [routerLink]="['book-list']"
without having to specify /str (no need to [routerLink]="['/str/book-list']"
):
<nav>
<div class="collapse navbar-collapse">
<ul class="navbar-nav mr-auto">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle">Items</a>
<div class="dropdown-menu" aria-labelledby=
"navbarDropdownMenuLink">
<a class="dropdown-item"
[routerLink]="['book-list']">Books</a>
<a class="dropdown-item"
[routerLink]="['cd-list']">CDs</a>
<a class="dropdown-item"
[routerLink]="['dvd-list']">DVDs</a>
</div>
</li>
</ul>
</div>
</nav>
<!-- Page Content -->
<div id="content">
<router-outlet></router-outlet>
</div>
Pros and Cons
The Client-side UI composition design pattern has some pros and cons. If you already have existing user-interfaces per microservices, and if each one is using totally different technologies (Angular vs React vs Vue.JS vs ...) or different framework versions (Bootstrap 2 vs Bootstrap 4), then aggregating them might be tricky and you might have to rewrite bits and pieces to have them compatible.
But this can actually be beneficial. If you don't have extended teams with hundreds of developers, you might end up being the one moving from one team to another one and will be happy to find (more or less) the same technologies. This is defined in the Chassis pattern. And for your end users, the application will have the same look and feel (on mobile phones, laptops, desktops, tablets), which gives a sense of integration.
With the way Angular Libraries are structured, having a monorepo is much easier. Of course, each library can have its own life cycle and be independent, but at the end of the day, it makes it easier to have them on the same repo. I don't know if you love monorepos or not, but some do.
Conclusion
We've been architecting and developing microservices for several decades now (yes, it used to be called distributed systems, or distributed services), so we roughly know how to make them work on the back-end. Now, the challenge is to be able to have a user interface which communicates with several microservices, developed by different teams, and at the same time feels consistent and integrated for the end-user. You might not always have this need (see how Amazon has totally different UIs for its services), but if you do, then the Client-side UI composition design pattern is a good alternative.
If you want to give it a try, download the code, install and run it (there is only the front-end, no back-end APIs). And don't forget to give me some feedback. I would be interested to know what you do to aggregate several user-interfaces into "a single one."
References
Building an Angular Library with the Angular CLI (version 6)
Why Google Stores Billions of Lines of Code in a Single Repository
This article is featured in the new DZone Guide to Microservices. Get your free copy for insightful articles, industry stats, and more!
Published at DZone with permission of Antonio Goncalves, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments