Microservice Front-End – A Modern Approach To The Division Of The Front

Microservice Front-End Main Logo

Microservice Front-End – A Modern Approach To The Division Of The Front

Microservice architecture has long been de facto the standard for the development of large and complex systems. It has a number of advantages: it’s a strict division into modules, and weak connectivity, and resistance to failures, and gradual output in production, and independent versioning of components.

However, often speaking about micro-service architecture, only the backend architecture is mentioned, and the front-end was, as it was, and remains monolithic. It turns out that we made a great backing, and the front pulls us back.

Today I will tell you how we did the microservice front in our SaaS solution and what problems we faced.

Microservice Front-End Photo 1

Problems

Initially, the development of our company looked like this: there are many teams involved in the development of microservices, each of which publishes its API. And there is a separate team that develops SPA for the end-user, using the API of different microservices. With this approach, everything works: the developers of micro-services know all about their implementation, and the developers of the SPA know all the subtleties of user interactions. But the problem arose: now every front-endman must know all the subtleties of all microservices. microservices becomes more and more, front-enders become more and more – and Agile starts to fall apart, as there is a specialization within the team, that is, the interchangeability and universality disappear.

So we came to the next stage – modular development. The front-line team shared with the subcommands. Each was responsible for its part of the application. It has become much better, but over time this approach has exhausted itself for a number of reasons.

  • All modules are heterogeneous, with their own specifics. For each module, their technologies are better suited. At the same time, the choice of technologies is a difficult task in the conditions of the SPA.
  • Since the SPA application (and in the modern world this means compiling into a single bundle or at least an assembly), then only the entire application can be made. The risk of each issue is increasing.
  • It is increasingly difficult to manage dependencies. Different modules need different (perhaps specific) versions of the dependencies. Someone is not ready to upgrade to an updated dependency API, and someone cannot do the feature because of a bug in the old dependency tree.
  • Because of the second point, the release cycle for all modules must be synchronized. Everybody is waiting for the laggards.

Downsizing Front-End

The moment of accumulation of critical mass has come, and the frontend has decided to divide into … frontend microservices. Let’s define what a frontend microservice is:

  • Completely isolated part of UI, in no way dependent on others; radical isolation; literally developed as a separate application;
  • Each front-end microservice is responsible for a certain set of business functions from beginning to end, that is, it is fully functional in itself;
  • It can be written on any technology.

But we went further and introduced one more division level.

The concept of a fragment

We call a fragment a certain bundle, consisting of a JS + CSS + Deployment Descriptor. In fact, it is an independent part of the UI, which must execute a set of development rules, so that it can be used in a general SPA. For example, all styles should be as specific as possible for a fragment. There should not be any attempts at direct interaction with other fragments. You must have a special method that you can pass to the DOM element, where the fragment should be rendered.

Thanks to the descriptor, we can save information about all registered fragments of the environment, and then have access to them by ID.

  • This approach allows you to place two applications written on different frameworks on one page. It also makes it possible to write a universal code that will dynamically load the necessary fragments into the page, initialize them and manage the life cycle. For most modern frameworks, it is enough to observe the “rules of hygiene” so that it becomes possible.

In cases where the fragment does not have the ability to “cohabitate” with others on a single page, there is a fallback script in which we draw a fragment in the frame (the solution to the accompanying problems is beyond the scope of this article).

All you need to do to the developer who wants to use the existing fragment on the page is:

1) Connect the microService platform script to the page.

script src="//{URL to static cache service}/api/v1/mui-platform/muiPlatform.js"/scriptgt;

2) Call the method of adding a fragment to the page.

window.MUI.createFragment( // fragment name "hello-label", // fragment model { text: "HelloLabelFragment text from run time" }, // fragment position { selector: ".hello-label-placeholder", position: "afterend" }) .then(callback);

Also for communication of fragments, there is a bus built between Observable and RxJs. It is written on NativeJS. In addition, the SDK includes wrappers for different frameworks that help to use this bus natively. An example of Angular 6 is the utility method that returns RxJs/Observable:

import {fromEvent} from "@netcracker/mui-platform/angular2-factory/modules/shared/utils/event-utils" fromEvent("event-namegt;"); fromEvent(EventClassType); 

In addition, the platform provides a set of services that are often used by different fragments and are basic in our infrastructure. These are such services as localization/internationalization, authorization service, work with cross-domain cookies, local storage and much more. For their use in the SDK, wrappers are also supplied for different frameworks.

Combine the Front-End

For example, we can consider this approach in the SPA admin (it combines various possible settings from different microservices). The content of each bookmark can be made a separate fragment, in which each microservice will be supplied and developed separately. Thanks to this, we can make a simple “cap”, which will show the corresponding microservice when you click on the bookmark.

Microservice Front-End Photo 2

Developing the idea of a fragment

The development of one bookmark by one fragment does not always allow solving all possible problems. Often it is necessary for one microservice to develop some part of the UI, which will then be re-used in another microservice.

And then we are helped too by fragments! Since everything a fragment need is a DOM element for rendering, we give any microservice a global API, through which it can place any fragment inside its DOM tree. For this, it is enough to transfer the ID of the fragment and the container in which it needs to be drawn. The rest will become itself!
Now we can build a “matryoshka” of any level of nesting and re-use whole pieces of UI without the need for support in several places.

Microservice Front-End Photo 3

It often happens that there are several fragments on one page, which should change their state when some general data on the page changes. For this, they have a global (NativeJS) event bus through which they can communicate and respond to changes.

Shared Services

In the micro-service architecture, there inevitably appear central services, the data from which are needed by everyone else. For example, a localization service that stores translations. If each microservice separately starts to climb behind this data to the server, we’ll get just the query shaft at initialization.

To solve this problem, we developed the implementation of NativeJS services that provide access to such data. This made it possible not to do unnecessary requests and cache data. In some cases, you can even output such data to a page in HTML in advance, in order to completely get rid of requests.

In addition, we developed wrappers for our services for different frameworks with the goal of making their use very natural (DI, fixed interface).

Advantages of front-end microservices

The most important thing we get from separating a monolith into fragments is the ability to select technologies by each command individually and transparently manage dependencies. But in addition, it gives the following:

  • Very clearly separated areas of responsibility;
  • Independent issuance: each fragment can have its own release cycle;
  • Increase the stability of the solution as a whole, since the delivery of individual fragments, does not affect others;
  • The ability to easily roll back features, roll them out to the audience in part;
  • The main strengths and benefits of micro frontends. that the independent development teams can collaborate on a front-end app more easily.
  • The fragment easily fits in the head of each developer, which leads to a real
    interchangeability of team members; In addition, each frontend can better understand all the subtleties of interaction with the corresponding backend.

The solution with a microsite frontend looks good. After all, now every fragment (microservice) can decide how to deport: whether it is necessary simply nginx for static distribution, a full-fledged middleware for aggregating requests to back-ups or supporting WebSockets, or some other specifics in the form of a binary data transfer protocol inside http. In addition, fragments can choose the methods of assembly, optimization methods and so on.

The disadvantages of front-end microservices

You can never do without a fly in the ointment.

  • The interaction between fragments cannot be provided by standard lamp methods (DI, for example).
  • How to deal with common dependencies? After all, the size of the application will grow like yeast if you do not take it out of fragments.
  • For routing in the final application, one should still answer.
  • What to do if one of the fragments is unavailable / can not be drawn.
  • It’s unclear what to do with the fact that different microservices can be on different domains.

Conclusion

Our experience of using this approach proved its viability. The speed of the withdrawal of features in production increased at times. The number of implicit dependencies between parts of the interface has been reduced to almost zero. We got a consistent UI. It is possible to conduct painless tests of features without attracting a large number of people to this.

Unfortunately, in one article it is very difficult to cover the whole range of problems and solutions that can be found on the path of repeating such an architecture. But for us, the pros obviously outweigh the disadvantages.