Micro Frontend Architecture: Replacing a Monolith from the Inside Out

November 15, 2019 0 Comments

Micro Frontend Architecture: Replacing a Monolith from the Inside Out

 

 

LOSA applications (micro frontends in general) are standalone Node.js services capable of server-side rendering a part or fragment of a webpage. A page can be made up of multiple LOSA services. These apps/micro frontends are built and deployed to a container that is independent and operating in a standalone manner

The same web page, composed three different ways, demonstrating an incremental migration path. Starting as a monolith rendered page, transitioning to LOSA micro frontends and finally ending up as a micro frontend vertical, completely replacing the monolith. Image Credit Robert Arkwright

The monolith remains responsible for handling an HTTP request object, sending the final response to the client. Micro frontends can stay behind a firewall within the cluster — only available directly to the legacy system until such a time when an API gateway and user authentication can be decoupled (or at least turned into an API endpoint). You don’t need many changes to prepare these frontends for their post-monolith life.

The Render Flow

Below is a modeled example of what a request/response could end up resembling.

First, a request is made:

GET/POST 'https://MFEwebsite.com/parts/header?format=json

Rendering a page can require a variety of data, any “missing” information that cannot yet be queried from a decoupled endpoint can be sent to the MFE (micro frontend) as props during the request. Here’s what the MFE does when a request is made, the request is passed through a piece of middleware which is responsible for rendering the React application, a query is made to any necessary APIs that are decoupled and its response is sent back to it as props. These props will make up window.INITIALSTATE.

The code

If you are in need of some inspiration on how to implement some of these template functions or filters, then Hypernova is worth looking at. I haven’t used Hypernova, always opting to build my own. I have implemented similar mechanisms into Rails, Node, and PHP backends. Due to the proprietary nature of various backend platforms, I’ll be using Hypernova’s examples to convey a rudimentary concept.

Here is what an MFE rendering endpoint would look like in express:

The request from another system, in this case the monolith:

GET/POST 'https://MFEwebsite.com/parts/header?format=json{ html: '<div> ... </div>', css: '/static/header.3042u3298423.css', js: '/static/header.idhf93hf23iu.js', initialstate: {items:[...]}

}

The middleware that handles the response:

export function exampleRenderAPIware(req, res) { const renderedMarkup = renderHTMLpage( req, this.index, intialstate, ); asyncRender.then(() => { const responseObject = { html: renderedMarkup, initialstate, js: jsResource, css: cssResource, }; res.status(200).end(JSON.stringify(responseObject)); });

}

Controllers making these initial POST requests should handle the responses, placing JS and CSS into the right locations and finally rendering the React app into the appropriate spot in the legacy template. Here’s an example of what that legacy template looks like now. The assets, usually handled by some other controller in your monolith, will be responsible for injecting those scripts and styles into what’s left on the legacy header and bottom of the body tag. Remember, the monolith still serves as the layout engine. We are replacing parts, and adding new features in a React SSR way. Eventually, these LOSA apps could be stitched back together under one MFE or with Webpack black magic I’m developing known as webpack-external-import.

What about migrating from template data to a new API?

When a new API is decoupled and brought online, what can be expected in migration?

When the monolith is providing the data to an MFE, Express.js accesses this information off the HTTP request body. Now express would need to asynchronously fetch from the API. Data formats might have changed, but React still receives props. Underwhelmingly straightforward.

Performance

Compared to older monoliths, the new LOSA (lots of small applications) architecture wasn't performant enough, taking 400–600ms for a part of the page to render. We used Async Worker structures, meaning that instead of one SSR’d app, we could ask multiple services to SSR different parts of the application. This made it very hard to bring production offline because a “production failure” meant maybe losing a sidebar or footer for 10 mins till it was fixed. Separation of concerns at its finest.

Here’s what I mean by LOSA async workers. There were many node services, each responsible for rendering a component/components.

Controllers (the grey gear) powering a legacy backend view can divert the view data to a post request instead of to a backend templating engine. Recycling data means there isn’t much effort required on the backend to support these mechanics. Avoiding major modifications will free up most of the backend engineering to focus on decoupling data providers, while the frontend can progress independently.

Since the view data was posted to an external react service, the response to that POST, which contains markup — is then passed to the backend templating engine, along with stylesheet, initial state, and CSS URLs. The templating engine now just renders the response from the POST request, thus decoupling your view or part of it from the legacy monolith.

React Render Time

React was slow! SSR just isn’t fast — so our new react solution LOSA architecture wasn’t performant enough to be viable. Our solution: Fragment caching inside react.


Tag cloud