Scaling frontends is hard, actually scaling all codebases is hard, frontends just happen to be particularly visible and have a tighter feedback loop and a higher rate of change. As with all codebases, it is in principle possible to scale development through standards and integration processes, but these are a poor substitute for communication. Once development moves beyond the scope of a single team, either progress slows to take into account of different processes or implementations drift away from each other over time. Teams need to find a way to operate independently towards a goal.
As with all problems with computer science, a potential solution comes in the form of a layer of abstraction. Backend codebases introduce abstraction in various ways to allow for a region of functionality to be logically separated. Abstraction layers take many forms: classes, interfaces, modules, libraries, microservices and subsystems each offering benefits and compromises. Units of re-use become used for subdivision of labour and comprehension within an organisation.
Most options for abstraction and organisation are available in frontend code, it’s code so why wouldn’t they be. However, recently some of the more architectural patterns such as microservices have begun to be explored.
In an attempt to simplify and separate codebases in a similar way to microservices, micro frontends split the UI into separate sections that can be delivered and updated independently. However, the benefits are tempered by the need for increased integration testing.
A traditional frontend would look like the diagram above, with the frontend implementation cutting across multiple microservices. This architecture tends to result in either there being a frontend team which can create a bottleneck or multiple teams each making alterations in a single frontend codebase, possible but logistically complex. Another side effect of this arrangement is that the frontend tends to become an orchestration point for the whole system, gradually absorbing functionality from the microservices, causing them to become anaemic database as a service. Everything comes full circle, and we now have a monolith in the frontend.
Micro frontends align user interface components with supporting microservices allowing a single team to own development end to end.
The separate user interface components need to be composed to create the whole user interface. There are multiple methods for composition typically using some form of container with on the server or in the client. Care must be taken to constrain interaction between components to reduce coupling, clearly defined interfaces, and events ease adoption.
Each of the teams has a full build pipeline for their component with a final integration step to create the complete application.
Design systems can be used across all of the components to ensure visual consistency between the components. Design systems can be developed by a central team or in a more distributed manner, the key to success is governance of changes and stakeholder management. Versioning and backwards compatibility also have a large part to play in adoption.
Large organisations may need to scale beyond the scope of single applications, and frequently need to create a seamless user experience across multiple subdomains.
Micro frontends will continue to work effectively within a subdomain, but across the whole domain, the integration overhead will quickly become unmanageable.
To work across multiple subdomains, other patterns must be used. Two key elements are required to deliver a consistent user experience across all of the subdomains:
If the two elements above are in place, it becomes relatively simple to integrate into the display with links between the subdomains being created in the same way they would be if the links were to an external site.
Background integration with APIs can be used to share information between subdomains, restful APIs that contain links to content help decouple the subdomains limiting the amount of data that needs to be shared for them to interoperate.
Events streams are also useful mechanisms to share data between subdomains, by definition events happened in the past and are immutable so they can be copied across the organisation domain freely. However, care needs to be taken interpreting events emitted by other subdomains.
If input is required from a user, they should be directed via a link to the subdomain that owns the data and process, avoiding issues with eventual consistency and also implementation of business process in multiple domains. Some components such as navigation may need to be shared across the entire domain with elements of configuration for local scope.
Integration via links also has the advantage that alternate technologies can be used across the domain, not requiring a single framework to be adopted.
An excellent example of an integrated user interface at this scale is Office 365, identity, design, and some navigation elements are shared across all of the products in Office 365. Still, they exist on multiple domains, e.g. office.com, sharepoint.com etc. Background integration via graph API’s allows the user interfaces to pull resources from other products, e.g. office.com pulls recent documents from SharePoint to be able to have personalised links on the Office homepage.
Caution should be used implementing unified user interfaces, use of a single framework can cause issues over time. To be able to unify multiple domains, it can often be simpler to rely on more straightforward techniques such as a single identity and design system, tightly scoping individual technology choices to a particular subdomain.