Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
November 18, 2021 09:05 pm GMT

Micro Frontend, but it's chaos!

But why do we need it?

As the frontend becomes larger, while holding multiple domains of responsibilities (for example navigation, user, authorization, e. g. ), the frontend application is developed, deployed and hosted as a single application.

Micro-frontends allows us to use those different applications not just on the code base, but also in development, deployment and serve, in order to provide:

  • Loosely coupled applications
  • Faster development, debugging, and testing flows
  • Performance smaller chunks
  • Full Isolation while testing, developing, and deploying

E2E Domains held by different teams

During this article we will review the frontend application that is built out of the shell and 3 applications that represent a domain:

  • Shell Used as the entry point for loading each of our micro-applications based on the URL path. The shell application will also trigger authorization for route guards.
  • Navigation Responsible for navigation logic and state, including the nav-bar component, navigation service
  • User Responsible for user logic and state, including the user query and store logic, user info component, and user management page
  • Feed Responsible for fetching and presenting the feed items, each item contains the user logic and state, including the user query and store logic.

Each application built out the following layers:
Micro frontends layers

  • Composition layer This layer holds a set of application pages with their corresponding routes
  • Widgets layer This layer holds a set of domain-related components used to build the different pages found on the composition layer
  • Business logic layer This layer holds a set of services and utilities responsible for the domain business logic.
  • Communication layer This layer holds a set of services that are used to communicate with the different service providers (Backend services for example).
  • Storage layer This layer holds the logic to persist data into the storage objects
  • In memory State, hooks e.g.
  • Disk local-storage, indexedDB, cookies e.g.

Wait, but where is the chaos?

The Chaos

Thanks to module-federation, we can load Micro-frontends applications during run time without the need to build the entire dependency graph.

This introduces a whole new aspect of stability issues of frontend applications:

  • What happens when we are deploying a new version of our application (User in our scenario)?
  • How can we identify affected areas?
  • How can we guarantee there are no breaking changes hidden behind each deployment?
  • How can we prevent tight coupling between multiple applications that are hosted together?

In our example, imagine a developer changed one of the widgets from the User application, this widget is consumed by both the Feed and the Navigation applications. Now, let's imagine the change the developer done is breaking the contract (component API inputs/outputs, aka. props).

This will lead to a runtime error while loading the new version within the existing applications.

And the result? Cascading failure of our frontend application after deployment of the new User application.

Big Failure

Tackling the problem
First, lets review the requirements we have from the micro-frontend applications:

  • Each application should be built, tested and served as a standalone unit.
  • A modification of a single application should be available to be used by any other application.
  • Application widgets and services should be reusable and interchangeable.
  • Encapsulation of application internal models and business logic Modifications shouldnt affect application consumers.
  • Identify dependency graph per modification will help us to trigger only the relevant tests suites and builds.

Following those items, lets review the approach from the previous section:
The approach covers bullets 1 to 3 from the requirements list. But, it still fails for both bullets 4 and 5 which promise us the stability of our product.

Lets review the different approaches to handle this chaos.

The libraries approach

In order to increase the stability of the application, we need to prevent hidden breaking changes.
With the libraries approach, this can achieve easily while using the npm package version. As each build of our applications is sealing the library version its using we can prevent consumption of library versions that might contain breaking changes.

Using module federation, we can set the shared libraries, as part of this configuration we can set the satisfied package version using the npm package versioning convention.

This approach helps us to break our monolith into 4 layers:
libs approach

  • Core Library This layer contains domain agnostic libraries, those libraries provide us the building block for our feature libraries layer.
  • Feature libraries layer This layer contains domain-specific business logic, storage logic, and widgets. Those widgets are developed based on the core libraries component kit and additional components that are part of the specific domain of responsibility.
  • Composition applications This layer contains domain-specific routes and pages. Those pages are built based on widgets, services, and business logic developed as part of the Feature libraries layer.
  • Shell The entry point of the application, usually acts as a container and a router to load each of the micro-applications based on the path.
  • The shell application might also trigger authorization logic.

Structure:

- apps  - user  - feed  - navigation  - shell- libs  - users-lib  - feed-lib  - navigation-lib  - auth

Webpack configuration:

plugins: [  new ModuleFederationPlugin({      name: "user",      filename: "remoteEntry.js",      exposes: {          './bootstrap': './apps/user/bootstrap.module.ts',      },      shared: share({        "@angular/core": { singleton: true, strictVersion: true, requiredVersion: '^12.0.0' },        "@angular/common": { singleton: true, strictVersion: true, requiredVersion: '^12.0.0' },        "@angular/common/http": { singleton: true, strictVersion: true, requiredVersion: '^12.0.0' },        "@angular/router": { singleton: true, strictVersion: true, requiredVersion: '^12.0.0' },        "@mfe/auth": { singleton: true, strictVersion: true, requiredVersion: '^1.0.0' },        "@mfe/user": { singleton: true, strictVersion: true, requiredVersion: '^1.5.0' },        ...sharedMappings.getDescriptors()      })  }),  sharedMappings.getPlugin()],

Advantages

  1. Shareable widgets, services, and pages (compositions) across applications.
  2. Breaking changes prevention Using a sealed version of the consumed library during the build.

Disadvantages

  1. Data corruption Possible due to collision between multiple versions of the same library (override the state, local storage e.g.).
  2. Bundle size increase Libraries might be loaded more than once due to different versions.
  3. Deployment graph complexity critical modifications require rebuilding and redeploying the entire dependency graph.

The anti-corruption layer approach

What is the anti-corruption layer?

An anti-corruption layer is a set of Public-APIs exposed by an application for integration use, those Public-APIs are acting as contracts in order to isolate the application internal models and business logic complexity, and are used as exported modules, components, faade*, and adapters* classes.

This layer can be uni-directional or bi-directional (fetch or ingest data).

Faade

A service that provides a simple interface to a complex application, encapsulates the complexity of initiating the application.
A faade might provide limited functionality, those are the required sub-set for integrating with the micro-frontend application

Adapter

A service that is responsible for covert the interface and the data model of an object to another structure/interface which is accepted by the consumers.

The updated 4-layers approach

Updated Approach

The only modification is casting the Feature layer from libraries to applications, this allows us to serve those widgets and services seamlessly to the consumers. Having said that we will still need to protect from breaking changes, here is where the anti-corruption layer is taking place

  • Core Library This layer contains domain agnostic libraries, those libraries provide us the building block for our feature libraries layer.
  • Feature application layer This layer contains domain-specific business logic, storage logic, and widgets.Those widgets are developed based on the core libraries component kit and additional components that are part of the specific domain.
  • The exposed logic and components are protected with an anti-corruption layer to prevent breaking changes.
  • Composition applications This layer contains domain-specific routes and pages. Those pages are built based on widgets, services, and business logic developed as part of the Feature application layer.
  • Shell The entry point of the application, usually acts as a container and a router to load each of the micro-applications based on the path. The shell application might also trigger authorization logic.

Structure:

- apps  - user    - src      - modules        - bootstrap          - bootstrap.module.ts    - public-api.ts    - public-api.d.ts  - feed     - src      - modules        - bootstrap          - bootstrap.module.ts    - public-api.ts    - public-api.d.ts  - navigation    - src      - modules        - bootstrap          - bootstrap.module.ts    - public-api.ts    - public-api.d.ts  - shell- libs  - auth

Webpack Configuration

plugins: [  new ModuleFederationPlugin({      name: "user",      filename: "remoteEntry.js",      exposes: {          './public-api': './apps/user/public-api.ts',      },      shared: share({        "@angular/core": { singleton: true, strictVersion: true, requiredVersion: '^12.0.0' },        "@angular/common": { singleton: true, strictVersion: true, requiredVersion: '^12.0.0' },        "@angular/common/http": { singleton: true, strictVersion: true, requiredVersion: '^12.0.0' },        "@angular/router": { singleton: true, strictVersion: true, requiredVersion: '^12.0.0' },        "@mfe/auth": { singleton: true, strictVersion: true, requiredVersion: '^1.0.0' },        ...sharedMappings.getDescriptors()      })}),  sharedMappings.getPlugin()],

Advantages

  1. Shareable widgets, services, and pages (compositions) across applications
  2. Seamless propagation of an upgrade
  3. Breaking changes prevention using Anti-Corruption layer.
  4. Refactor is becoming more simple thanks to encapsulation.

Disadvantages

  1. Another layer to be maintain
  2. Education and learning curve
  3. Integration testing is required to promise unbreaking changes

Bonus


Original Link: https://dev.to/0xf10yd/micro-frontend-but-its-chaos-68j

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To