- Background
- Microfrontends
Single-page applications (SPAs) consist of a single or a few JavaScript files that encapsulate the entire frontend application, usually downloaded upfront delivered by CDNs. They communicate with backends via APIs and handle view changes via fully managed routing.
SPAs are generally a single frontend project with backends which may range from monoliths to microservices. These frontend projects are generally owned by a single team. As project scope grows such projects become increasingly difficult due to:
- Maintainability concerns
- No clear Ownership in cases of multiple products involvement
- Conflicting priorities
Micro-frontends are an emerging architecture inspired by microservices architecture.
The main idea behind them is to break down a monolithic codebase into smaller parts, allowing an organization to spread out the work among autonomous teams, whether collocated or distributed, without the need to slow down their delivery throughput.
If the business logic and the code complexity are reduced drastically, the overhead on automation, governance, observability and communication have to be taken into consideration.
- Domain driven design
- Model projects around business domains
- Serves as a good separation of concern for projects
- Grants teams ownership of their domains
- Follows Conway's law
- Culture of Automation
- Culture of automating the deployment of independent units in different environments
- Hide Implementation Details
- Encapsulate services to hide their details for other services
- Helps focus on implementation
- Limit impact of technical decisions
- Helps each team follow their own operations and timelines
- Decentralize All the Things
- Architecture decisions, frameworks, governance
- One-size-fits-all approaches no longer required
- Deploy Independently
- Orchestrate independently of other projects
- End to end operational ownership
- Isolate Failure
- Prevent failure cascades
- Highly Observable
- Distributed nature requires high observability to track health
The framework for architectural decisions consists of these key points:
- Define what a microfrontend is in your architecture
- How will individual microfrontends compose
- How will routing work for microfrontends
- How would communication work between microfrontends
The individual microfrontends need to be visually split amongst themselves depending on how the projects and the business is structured. There are two possibilities that one can consider:
With the horizontal split, multiple micro-frontends will be on the same view. Multiple teams will be responsible for parts of the view and will need to coordinate their efforts. This approach provides a greater flexibility in the context where different feature-sets are handled by different teams.
In the vertical split scenario, each microfrontend is rendered into a different page. In such a structure each team can be responsible for their business domains with central authentication and user onboarding experience. Domain-driven design (DDD) takes center-stage in determining the split.
The 3 different ways of composing frontend applications are:
- Client side composition
- An application shell loads microfrontends inside itself
- Possibilities include:
- single-spa: control routing cooperatively with hooks for major web frameworks
- Iframes
- h-include: uses placeholder tags that will create an AJAX request to a URL and replace the inner HTML of the element with the response of the request
- Edge-side composition
- Assemble views using ESI supported CDNs
- Not exactly a standard implementation in real world
- Server-side composition
- Handle all rendering in the backend
- Can be paired with CDNs with large TTLs to handle asymmetric traffic
Based on how microfrontends compose and their splits, there are multiple corresponding routing options.
Split | Compose | Routing |
---|---|---|
Horizontal | Client side | Router Hooks |
Vertical | Client side | Rendering Hooks |
Horizontal | Edge Side | Custom |
Vertical | Edge Side | Custom |
Horizontal | Server Side | Custom |
Vertical | Server Side | nginx |
There are multiple ways of communication between multiple microfrontends. Their choice is closely tied to how the microfrontends are split.
Split | Solution | Libraries |
---|---|---|
Horizontal | Eventbus pattern | PubSubJS |
mitt | ||
Custom events | ||
eventemitter3 | ||
postal.js | ||
Vertical | Local storage | |
Cookies (auth tokens) | ||
Query strings |
- Deciding the split
We choose the vertical split scenario for supporting 4 classes of components:
- Core components
- Each product e.g. bridge, bbps, dg, fd etc
- Supporting components
- Admin console (s)
- Analytics systems
- Support ticket raising system
- Generic components
- Login & authentication
- User creation
- Auxillary components
- Demos
- Private API documentation
- Internal utilities
This structure solves for:
- Allowing diverse technical stacks
- Enable business to define separation of concerns
- Let current team structures own and operate with minimal conflicts
- Enable a single user signup experience and IAM across the platform
Trade-offs:
- Requires a central point for navigation into individual products, say a "products" page
- Requires all pages to redirect back to the "products" page
- Design consistency becomes a choice and hence enforcement a necessity for the core subdomain
We choose client-side composition because:
- Wide range of options and a community building tools for the same
- Other options have to be built in a completely custom manner
Trade-offs:
- May lead to large initial page load times
- Can be solved by using lazy loading
We choose router hooks for the following reasons:
- Our options are narrowed to it by previous choices
- Encourages teams to use the most popular well-supported frameworks
Trade-offs:
- Possibility of conflict in routes
- Possibilities of bugs in attempts to override routing
We choose to use cookies, local storage and query strings to communicate between microfrontends because:
- Standards compliant and widespread availability
- Does not introduce any new library or component into the architecture
Trade-offs:
- Needs uniform data sharing semantics
- Data consistency and schema conflicts may spill across projects leading to failure cascades
The framework for taking decisions on automation depends of these key points:
- Type of delivery artifact
- Choice of testing strategy
- Degree of coupling with other microfrontends
The type of delivery artifact depends on the chosen split and composition method.
Key considerations here include:
- Enable fast iteration
- Enable fast feedback loops
- Encourage standardization for maintainability of automation
- Empower teams to own artifact creation process
- Developer experience
The testing strategy has an large impact on designing automation.
- Environments strategy
- Maintain different environments - testing, sandbox and production
- The testing environment is bleeding edge
- Version control strategy: Mono-repo or poly-repo
- Defines testing artifact
- Determines whether each microfrontend can be independently tested
- Tight coupling
- Requires an additional step of composing and building the final application
- Loose coupling
- Each microfrontend's automation can be operated in isolation
We choose babel compiled javascript and other assets delivered via a CDNs per microfrontend as the delivery artifact.
We choose to stick to the environments strategy as it is already in place.
We choose to have minimal to no coupling between microfrontends.
The entire delivery process is described in the following diagram:
A more involved versioned operations pipeline would look like:
We choose single-spa for integrating with routers of various frontend frameworks.
Components to be built:
- Main app: The main app corresponds to the single-spa root config
- Auth: A core component that handles authentication, IAM and user signup as the single-spa root application
- Other applications
Framework support:
- Isomorphic Layout Composer / ILP
- Main USP: Isomorphic apps with backend rendering
- We do not intend the console to be backend rendered
- Opencomponents
- Very nascent stage
- icestark
- Not much ecosystem adoption