Deciding when to move from a monolithic to a distributed frontend architecture is an important technical decision as any company scales. Here’s what you need to consider before making the move.
Monolithic architectures have long been the standard for building web applications. However, as businesses strive for greater scalability and resilience in their systems, limitations have become apparent, driving software developers towards more distributed architectures to tackle these issues.
By breaking down a large application into smaller, independent microservices that communicate with each other over a network, developers can build applications that are more resilient, scalable, and easier to maintain than their monolithic counterparts.
Company growth life cycles
Which of these architectures are the right fit for your organization will likely come down to where you are on the maturity curve.
The life cycle of a company typically starts with the startup phase, which is characterized by a focus on rapid product development, customer acquisition, and establishing a foothold in the market.
Although a monolithic architecture has its downsides, it can still be a suitable option for new companies, as it enables the design of software applications with tightly integrated components that are deployed as a single unit. This has several advantages:
A monolithic architecture is straightforward to understand. It is easier to design, develop, and maintain a monolithic application than a micro-architecture application. New companies often have limited resources and expertise, and a monolithic architecture allows them to focus on building a working product quickly.
Since monolithic applications are deployed as a single unit, they require fewer resources and hardware than a microservices architecture. New companies with limited budgets benefit from this approach as they can save money on infrastructure costs.
While a monolithic architecture is not as scalable as microservices, it can still handle moderate levels of traffic. Often, companies don’t need to worry about scalability in the early stages of development and so a monolithic architecture can provide just enough capability until further growth.
Since a monolithic architecture is simpler than microservices, new companies can rapidly prototype their product. This agility can be critical for companies trying to find product-market fit, or experimenting with different features.
It is easier to secure a single application than a distributed system, which makes maintaining the application’s security easier.
As a company grows, it begins to expand rapidly and generate significant revenue. During this phase, the focus is on scaling the business, hiring new employees, creating new development teams, and building new products or services to meet the needs of an expanding customer base.
Once the company has reached a certain level of maturity, growth slows down and the focus shifts to maintaining market share and maximizing profitability. During this phase, the company may need to pivot its business model or develop new products and services to stay competitive.
Determining the optimal time for the migration
It is extremely important to get the timing right when migrating from a monolithic application to a microarchitecture. If you move too early, then you may not have enough resources to make the migration a success. On the other hand, if you wait too long a monolith can start to be monstrously complicated, taking lots of effort to decouple into simple pieces.
When should a company consider migration to microarchitecture?
If delays commonly arise when delivering new functions and addressing business needs, increasing the number of engineers may not be effective. Instead, the organization should focus on changing the processes in place.
Indicators that a system is no longer scalable include:
- It is challenging to maintain the system.
- The system is vulnerable and often experiences critical incidents when a developer makes changes in one place without realizing the potential for breakage elsewhere.
- The continuous integration/continuous deployment (CI/CD) process is overburdened, with engineers having to wait in line to deliver their features or bug fixes.
- It is not possible to switch between different technological stacks without overhauling the entire system.
- Updating the application and its versions is difficult.
- The codebase is confusing when multiple teams are working on it simultaneously.
- There is abandoned code with no team taking responsibility for its maintenance or improvement.
- There is no clear responsibility or accountability between teams.
- Multi-functional teams, such as those responsible for stream alignment, are also tasked with enablement functions or platform jobs.
Making technological errors during the transition period can be extremely costly. During the startup phase, it is essential to get the timing right. Missteps or oversights may prevent future implementations of features due to an architecture that cannot support them.
One commonly implemented solution to help tech leadership adapt to the current state is the system’s migration from a monolithic architecture to a microservices architecture for back-end technologies, or micro frontends (µ-frontends) for front-end technologies.
Microarchitecture in frontend
µ-frontends is an architectural style where independently deliverable front-end applications are composed into a greater whole.
There are several different approaches to implementing µ-frontends, which involve breaking down the front-end application into smaller, more manageable parts. Here are some common approaches:
Inline frames (iFrames) are a commonly used approach to implementing µ-frontends, where each µ-frontend is loaded into a separate iFrame on the page. This method allows each µ-frontend to function independently, but it can also result in performance issues (a slow app or issues with different parts loading at different times) and increased complexity.
Web components are a set of standards-based application programming interfaces (APIs). These sets of features allow developers to create reusable, encapsulated components that can be applied across different web applications. This approach provides greater modularity and flexibility, but it can also be more complex to implement.
Build-time integrated µ-frontends
In this approach, each µ-frontend is packaged into its own bundle and compiled by the content application’s orchestrator. While it is straightforward to introduce a new version of a µ-frontend, doing so requires rebuilding the orchestrator.
Run-time integrated µ-frontends
This approach involves including each µ-frontend in the container application through a content delivery network (CDN). The container application determines which µ-frontend should be mounted and calls the relevant function to instruct the µ-frontend on when and where it can render itself.
This is perhaps the most flexible method for incorporating µ-frontends into an application since it does not require reconfiguring the container app. Any changes made to each individual µ-frontend will be immediately visible as soon as the µ-frontend build is completed.
Requirements for scalable µ-frontends architecture
To ensure your architecture is scalable you need to keep these next requirements in mind:
The architecture should be able to be divided into small code chunks; this will keep the code more organized according to functional criteria, such as business domain, or a team’s responsibility area. A smaller codebase is easier to read and understand.
The µ-frontend codebase must be isolated to enable teams to adopt their own working strategies. Each team can specialize in its domain and have complete ownership of a vertical slice of the application. This approach reduces the need for coordination between teams and minimizes accidental interference from other teams. By using µ-frontends, Conway’s Law – that organizations will design systems that copy their communication structure – can be applied, allowing the application structure to align with team structure .
µ-frontends follow the same approach as microservices by taking responsibility for their own deployment. This gives teams the flexibility to release parts of the application independently, rather than redeploying it in its entirety.
By deploying smaller pieces of the codebase, teams can independently release updates without coordinating with other teams or conforming to a company-wide release cycle. If problems occur during deployment, it’s easy to rollback without affecting the work of other teams. Additionally, since the codebase is split into smaller pieces, µ-frontends can be deployed much quicker.
Smaller testing surface area
Testing small amounts of code is generally easier than testing large applications, which can lead to better testing coverage across the entire system. By breaking the codebase into smaller pieces with µ-frontends, it becomes easier to ensure that all parts of the code are covered by tests.
Possibility to use different front-end frameworks
Unblocking pathways to different technologies is a huge benefit for managers and teams. It helps the company become more flexible.
One benefit of using µ-frontends architecture is that it isn’t tied to specific technologies or frameworks, making it easier to find dedicated teams to work on them. This approach also enables teams to replace inappropriate technologies or frameworks with newer, more suitable ones. µ-frontends also provide a low-risk way to test new technologies without having a significant impact on the business.
Inter-communication for µ-frontends
To build the proper architecture there are some inflexible requirements:
Inter-communication between children and orchestrator
- There should be almost zero coupling between an orchestrator and the children (an µ-frontend that makes up a part of the application).
- The orchestrator should not know how the child µ-frontend is implemented. If the child’s implementation is changed but provides the same interface, nothing should be broken.
- Children’s µ-frontends must provide a unified interface to be included in the orchestrator.
- All essential communications between children and the orchestrator are done with callbacks.
- The orchestrator should be able to decide to always use the latest version of a child or specify a specific version.
Inter-communication between children’s µ-frontends
- Zero coupling between children µ-frontends. Each child should not know about other µ-frontends and must be able to work in isolation.
- No imports from one µ-frontend into another.
- Shared libraries between children are possible.
- All CSS code must be scoped to exclude overriding style properties.
The four layers of abstraction
In our company, the approach has been to develop four layers of abstraction in µ-frontends architecture: A Frontend-as-a-Service (FaaS) layer, the application layer, page layer, and widget layer.
The FaaS layer contains the most common pieces of the application which could be used across multiple frontends. This layer contains the µ-frontend’s connection rules, common libraries, authentication, and more.
If we need to change common parts of an application, we update the FaaS layer and then synchronize all application layers with it. This means that if we have five applications with common code, we can update it just once in the FaaS layer and all applications should then be updated.
This is the orchestrator of the entire frontend application. As described above, its role is managing all connected µ-frontends, keeping shared data, and being responsible to communicate between the browser and the application. Also, on this layer, the application can have a user interface (UI) layout.
Most web applications have more than one page. The page layer is responsible for rendering the content of the entire page, except for certain common parts (such as app layout, header, and sidebar). The layer is a child for the application layer orchestrator and at the same time, it serves as an orchestrator for the widget µ-frontends.
This is the highest layer within the application. It could be a small UI element shared between multiple pages or even between multiple applications.
In conclusion, moving from a monolithic architecture to a distributed architecture in frontend can bring numerous benefits to software development. By breaking down the application into smaller, more manageable parts, it becomes easier to develop, test, and maintain. A distributed architecture can also enable better scalability, availability, and fault tolerance, allowing the application to handle more users and traffic.
However, implementing a distributed architecture requires careful planning, design, and coordination between teams, and it may involve using new technologies and tools. Therefore, it is crucial to weigh the benefits and challenges of distributed architecture before making the switch.
A distributed architecture is a powerful approach that can help organizations build more robust and flexible frontend applications that can adapt to changing requirements and user needs.