State Management in Micro Frontend Architectures
Prerequisites:
- Basic understanding of State Management.
- Familiarity with Redux.
Now, let’s jump into the exciting world of Micro Frontends 🚀.
Micro Frontends in a Nutshell
Micro Frontends may sound complex, but don’t worry; we’ll simplify it for you. Inspired by the concept of Micro Services, coined by Martin Fowler, Micro Frontends aim to break down monolithic frontends into smaller, manageable pieces. These pieces can be independently developed, tested, and deployed by smaller, specialized development teams.
Imagine a banking customer portal. When you log in, you can perform various tasks like checking your balance, creating fixed deposits, or updating your contact information. In a modern enterprise setup, each of these tasks should be treated as separate bounded contexts. They are deployed as isolated services with standardized interfaces for communication — they become Micro Services. While the backend follows this Micro Service architecture, the frontend, in a traditional setting, might have been a monolith, leading to problems like maintainability issues, accidental code changes, unintended coupling, and complex deployments.
Micro frontend architecture solves these problems by slicing the monolith into components, with multiple teams working on each piece. These pieces have their development teams, deployment cycles, and even their UI frameworks.
Now, let’s talk about why State Management in a micro frontend architecture is challenging. A common approach is to use a global state, often implemented with libraries like Redux, encompassing all micro frontends. While this simplifies some concerns like communication and subscription, it introduces unwanted coupling.
Imagine it as sharing a single database among multiple microservices in a Micro Services architecture. Using Redux for global state means having a common, extensive Reducer to manage actions for the Global Store. Each micro frontend becomes dependent on this central piece, increasing inter-module coupling.
Hence it makes sense for each self-contained micro frontend to maintain its store. Although this allows the applications to remain isolated, it introduces a new problem. How will these self-contained micro frontends communicate with each other? Let’s say both retail banking and investment banking needs to show a notification to the user. How will the Shell application (let’s assume the shell application is another isolated micro frontend responsible for showing notifications) coordinate this information? To further complicate matters, these notifications are actionable (clickable), and the parent micro frontend needs to react to the user clicks. So we have 3 self-contained, isolated applications, but they need to have a line of communication for a common process.
In a Micro Frontend environment, individual applications should not require knowledge of the existence of other applications or their respective states. However, they must establish a consistent method for interacting with one another. Therefore, it’s crucial to strike a balance 🍬 between maintaining isolation and enabling effective inter-communication among micro frontends
Virtual Global State 🌐
Now there are multiple ways of solving this problem, and the solution depends on how complicated a form of communication is required. The solution that I am about to describe is a tried-and-tested mechanism applied in a real-world micro frontend styled application in our org.
The simplest way of solving the problem would be to send simple events (custom events in JS). Although this is a good idea for cross-app communication, some of the other complicated scenarios are not solved by simple events. Let’s say your application wants to read the state of some other application, subscribe to changes made in some application, or send out a global message that multiple applications might need to react to. Simple events can’t be applied to all such scenarios.
We solved this problem by creating a virtual global store amalgamation of various stores and then establishing a line of communication between these stores without dictating terms. These individual stores don’t even need to know about the existence of the other stores.
Behind the Scenes 🎭
Read: Each micro frontend can maintain its store containing the necessary actions and reducers. These applications can register their stores with the shell framework. The framework maintains the mapping between each application and its registered store. It exposes methods for an application to read the state of another application’s store, presenting it as the Global State of the platform.
Subscribe: Applications can attach callbacks to other app’s states. The framework attaches the callback as a listener to the required store, allowing an application to react to changes in another application’s state.
Dispatch: Inter-app communication happens through actions. However, allowing one application to dispatch actions into another’s stores isn’t wise. To address this, ‘Global Actions’ were introduced.These actions can be dispatched by any application but are owned and defined by individual apps. The framework maintains a list of global actions for each store and allows other apps to dispatch them. The logic to handle the action remains within the individual app store.
Now that we’ve explored the intricacies of State Management in Micro Frontends, it’s time to introduce you to a powerful tool that can make this process more accessible and efficient: Redux-Microfrontend.
What is Redux-Microfrontend?
Redux-Microfrontend is a comprehensive library designed to streamline State Management in Micro Frontends. It simplifies the complexities of coordinating state between isolated frontend modules, enabling seamless communication and maintaining the autonomy of individual micro frontends.
This library can be used for using Redux in a Micro Frontend based architecture. Micro Frontends is an architectural pattern for breaking up a monolith Frontend application into manageable, decoupled and smaller applications. Each application is a self-contained and isolated unit. Generally, a common shell/platform application is used to host these small units to provide a common experience for the end-users.