Micro-frontend “Blackbox Pattern”

Naim Gkamperlo
4 min readMay 4, 2020

--

TL;DR: In the Micro-Frontend world there is a need for a blackbox (hence the name of the pattern) which has an input, renders itself in the DOM and has its own workflow and produces an output for every other micro-frontend to get its results.

The problem

In Micro-Frontends, hereafter called “MFEs”, world there is usually a need to create a micro-frontend which can utilised by all the other micro-frontends. However, this is considered an anti-pattern because there are two famous approaches on micro-frontend implementation:

  • No communication between MFEs
  • Full information exchange between MFEs

So, our team came across a product requirement where neither of the above solutions was an exact fit. The requirement was to create a component which does the following:

  • Is invokable by other MFEs
  • Is invokable by every possible front end language (Javascript, Typescript, Elm etc)
  • Renders itself in the DOM
  • Deletes itself from the DOM once its job finishes
  • Always provides the latest version of it to all its consumers (other micro-frontends)

What the team added on top of these requirements was the following:
Have the least possible communication with other MFEs
as in the opposite scenario a lot of issues could be raised:

  • …what are the names of these custom events again?
  • …I cannot make a proper communication between Angular and React MFEs
  • …misuse of local storage
  • …misuse of pub/sub mechanisms
  • …misuse of web socket mechanisms
  • overengineering

As a result the last requirement of this MFE was:
Have only one input and only one output event to communicate with its consumers

The solution

We first thought to just create another MFE…

which is invoked by our central orchestrator

However that would be a bad practice due to the following:
1. MFEs should not share state between each other
2. This specific MFE should be invokable by every other MFE in the project

We then thought of another approach

What if we created an MFE which has the following:
1. A single input (which could probably be a browser’s custom event)
2. Render itself on the DOM (and specifically in a single element provided by each MFE that invokes it) as a vanilla JS application
3. A single output (which is a browser’s custom event)

And so we did. By implementing the use of the blackbox pattern, we have tackled all of our requirements:

Invokable by other MFEs

The implementation can be invoked by all of MFEs through browser’s custom events

Invokable by every possible front end language

The implementation is invokable by every front end language as it is compiled to vanilla JS

Renders itself in the DOM

The only input needed by the blackbox is the DOM element’s id so that it is able to mount its whole logic in a single element.
Even if there is a need for SPA, the whole routing of the blackbox’s MFE can be directed from within.

Deletes itself from the DOM once its job finishes

Once the blackbox’s job is finished, along with firing the ‘-finished’ event, it unmounts the DOM element that used, along with its scripts.

Always provides the latest version of it to all its consumers

The team has created a tool which always provides latest versions of all MFEs, so we just used our current pipeline.

A more concrete example

Let’s say that in a banking application, every MFE ( accounts MFE, cards MFE etc ) needs to display a UI for a money transaction (let’s name this TransactionsProvider ) to take place and get the result of the transaction ( successful , error , error codes etc )
By utilising the blackbox pattern, the following solution would be given:

  • Invoke the transaction UI with a single event i.e : ‘openTransaction’ along with the following args:
{
fromMFE:'accounts',
payload:{
fromAccount:'accountA',
toAccount:'accountB',
amount:50,
currency:'USD'
}
}
  • TransactionsProvider would render itself. The user would perform every action that the UI dictates and after a while makes a money transaction. Upon close of the TransactionsProvider UI a new custom event fires which is called ‘transactionFinished’.
  • ‘transactionFinished’ event is fired upon close of TransactionsProvider with the following result:
{
resultCode:'success',
payload:{
status:'pending'
}
}
  • The accounts MFE that invoked the TransactionsProvider should continue its logic with the blackbox’s result.

All the above can be depicted as follows:

Last words

Utilising blackbox pattern has the following perks:

  • Single repository for the blackbox
  • Minimal communication between micro-frontends
  • It is technology agnostic as it is rendered by itself and can be communicated with simple vanilla JS functionality
  • No local storage is used and polluted
  • Minimum pub/sub mechanisms used
  • No web socket mechanisms used
  • No over-engineering
  • Is invokable by every MFE
  • Renders itself in the DOM
  • Deletes itself from the DOM once its work is finished
  • Always provides its latest version to all of its consumers (other micro-frontends)

…and the following cons:

  • same dependencies are loaded twice in case of using the same technology
    i.e if the blackbox is implemented in Angular and the caller of it is Angular, then Angular library will be loaded twice.
    This can be resolved if and only if same framework is used for every MFE in the project , as then it can be provided by the caller MFE as an external dependency.

Code is not included as this post mainly looks on how to approach MFE issues that come up by any frontend technology.
However a demo github repo is around the corner!

Thanks for reading!

--

--