Breaking Monolith: What, Why & How?
As applications grow in time, adding new features, with complex logic, many different interactions and coupling between components, it becomes very difficult to deal with them. The ever-growing demand for the innovation, reinvention, and scaling of a business and the software system that it relies on is a significant challenge. That challenge is compounded by the fact that as software adapts to changing business needs, it gets more complex and more complicated to maintain as the business grows.
With Cloud becoming the natural choice of infrastructure, bringing the apps to cloud and be efficient on cloud is another dimension where organizations are forced to reconsider the architecture of the applications such that they are cloud native.
I think this gives us right context to introduce the word Monolith.
Ok , what is monolith?
What is Monolith?
Has your application become too large and complex to support?
Are you nervous when new versions are released because new things always seem to break?
Are you frustrated because it takes so long to add features to your application?
Are you worried because support costs for your application keep going up and up?
Are you thinking how best to leverage cloud features once your application moved to the cloud?
If your answer to most of these questions is YES, then you can consider you have Monolith.
Almost every enterprise application has a similar kind of layered architecture:
Presentation: The user interface.
Business logic: The application’s internal business logic.
Database access: Almost all applications need to access DB, either SQL or NoSQL.
Application integration: Quite often, the application needs integration with other applications. This is usually achieved via web service calls (SOAP or REST), or via messaging.
A monolithic design can make sense as a starting point for an application. Monoliths are often the quickest path to building a proof-of-concept or minimal viable product. In the early phases of development, monoliths tend to be:
Easier to build, because there is a single shared code base.
Easier to debug, because the code runs within a single process and memory space.
Easier to reason about, because there are fewer moving parts.
Even though applications have clear, logically modular architecture, but usually, most of the application is packaged and deployed as a monolith. Monolithic architecture worked successfully for many decades. In fact, many of the most successful and largest applications were initially developed and deployed as a monolith. Many large-scale enterprise applications of big companies are still being deployed as a monolith.
But with the changing market and with the emergence of new technologies, there has been a paradigm shift in the way IT industries work. There are some serious issues with monolithic architecture, which most companies are trying to address these days. These problems can become an obstacle to future growth and stability. Teams become wary of making changes, especially if the original developers are no longer working on the project and design documents are sparse or outdated.
Why do you need to break it?
Monoliths typically have these types of problems:
New features take too long to bring to market.
Most new releases break features that weren’t broken before.
Burn-in time for new developers is much longer as there is so much to learn.
Fixing defects on monoliths is often more difficult.
The system is in need of a rewrite. All that you need to do is make a new system that does what the old one did, right? But what seems to be a straightforward task proves to be difficult, risky, and complex.
You find more things under the surface than you expected, and the effort to replace the application now seems overwhelming and dangerous.
What will happen if you break the monolith?
Breaking the monolith to developing a single application as a suite of small services/components, each running in its process. Indeed, a service/component decomposes a monolithic application into a collection of individual, loosely coupled components often called as microservices, to encapsulate business capabilities in a particular domain to fill objectives and responsibilities.
These services/components are built around independently deployable by fully automated deployment machinery. Basically, instead of developing software as a set of functionality to be completed. Microservices philosophy is design and bound around business capabilities. Componentized functionalities often called as Microservices are very beneficial, some of the important benefits of using are listed:
On-boarding new members on the system are much easier.
It can have different tech stacks such as Pega, python and java.
Business logic can be reused, whenever they are needed by other parts of the system.
Each part can deploy independently and time to market decreases radically
Easier to scale and distribute.
Accelerating the pace of change and escaping the high cost of change.
Fault-tolerant. Services are isolated and are more tolerant of failure.
This new state of architecture lets you split applications into distinct independent services, each managed by individual groups within your software development organization. They support the separation of responsibilities critical for building highly scaled applications, allowing work to be done independently on individual services without impacting the work of other developers in other groups working on the same overall application.
How to break the monolith?
Breaking down a monolith requires the strategy of a business analyst, the precision of an engineer, the vision of an architect, the imagination of the artist, and the courage of a madman. It’s also difficult, as monoliths often have no proper separation of concerns, no clear domain design, and in a few cases, bugs that were kept for historical reasons.
Breaking a monolith into components/services requires significant time and investment to avoid failures or overruns. To ensure that any migration is successful, it’s good to understand both the benefits and also challenges that microservices bring. There are multiple design patterns to break the monolith and we can use them to strategize the plan. Often we need more than one of these patterns.
Any migration strategy should allow teams to incrementally refactor the application into smaller services, while still providing continuity of service to end users. Here’s the general approach:
· Stop adding functionality to the monolith.
· Split the front end from the back end.
· Decompose and decouple the monolith into a series of microservices
If we don’t want to add functionality to monolith, we need to envision the new application with components where all microservices advantages included and start pealing the low hanging fruits out of the monolith and add to this new application, which is often called as Strangler application.
Domain Driven Design
What If we don’t know how to peel or extract and which part of the monolith, which is very common scenario?
As the project and product mature, hopefully, their domain takes shape and settles, becoming more stable. By now, some parts of the domain will be more active than others. We can rely on these domains to demarcate the functionalities and identify components or services. DDD helps us to decompose the application as functional components with business values called as Domains
Domain-dr iven design (DDD) provides a framework that can get you most of the way to a set of well-designed microservices. DDD has two distinct phases, strategic and tactical. In strategic DDD, you are defining the largescale structure of the system. Strategic DDD helps to ensure that your architecture remains focused on business Capabilities. Tactical DDD provides a set of design patterns that you can use to create the domain model. These patterns include entities, aggregates, and domain services. These tactical patterns will help you to design micro-services that are both loosely coupled and cohesive.
Start with a ubiquitous language, a common vocabulary that is shared between all stakeholders.
Identify the relevant modules in the monolithic application and apply the common vocabulary to them.
Define the domain models of the monolithic application. The domain model is an abstract model of the business domain.
Define bounded contexts for the models. A bounded context is the boundary within a domain where a particular domain model applies. Apply explicit boundaries with clearly defined models and responsibilities.
Within a bounded context, apply tactical DDD patterns to define entities, aggregates, and domain services.
Use the results from the previous step to identify the components/microservices in your application
Anti-Corruption Layer (Glue Code) pattern
Now we have applied DDD or we know what to be redesigned as a functional component and this component is ready. But sometimes this new component/service might need to access the data/resources from monolith. But the monolith old modeled data models might influence or corrupt these newly developed components. So we need an intelligent layer between the legacy and new service/component which acts as proxy and avoid the corruption of new components due to old/legacy blocks.
Once all the identification of components using DDD is done and implementation is completed, then this Glue Code/ACL can be removed safely and all traffic shall direct to new application (called as Strangler Application) which replaces the monolith.
Glue Code integrates (adapter pattern) the component/service with the monolith. A component/service rarely exists in isolation and often needs to access data owned by the monolith. The glue code, which resides in either the monolith, the service, or both, is responsible for the data integration. The service uses the glue code to read and write data owned by the monolith.
The glue code is also called an anti‑corruption layer. That is because the glue code prevents the service, which has its own pristine domain model, from being polluted by concepts from the legacy monolith’s domain model.
The glue code translates between the two different models. Developing an anti‑corruption layer can be a non‑trivial undertaking. But it is essential to create one if you want to grow your way out of monolithic hell.
Through the process of refactoring, teams can inventory the monolithic application and identify candidates for microservices refactoring while also establishing new functionality with new services using this pattern.
Split Front-end and Back-end/ API Gateway Pattern
Another strategy that shrinks the monolithic application is to split the presentation layer from the business logic and data access layers. A typical enterprise application consists of at least three different types of components:
· Presentation layer/UI — Components that handle HTTP requests and implement either a (REST) API or an HTML‑based web UI. In an application that has a sophisticated user interface, the presentation tier is often a substantial body of code.
· Business logic layer — Components that are the core of the application and implement the business rules.
· Data‑access layer — Components that access infrastructure components such as databases and message brokers.
Application (business) layer tends to be the components that are core to the application and have domain logic within them. The business tier has a coarse‑grained API, which encapsulate business‑logic components. These coarse-grained APIs interact with the data access layer to retrieve persisted data from within a database.
There is usually a clean separation between the presentation logic on one side and the business and data‑access logic on the other. These APIs establish a natural boundary to the presentation tier, and help to decouple the presentation tier into a separate application space. One application contains the presentation layer. The other application contains the business and data‑access logic. After the split, the presentation logic application makes remote calls to the business logic application.
This diagram also introduces another layer, the API gateway that sits between the presentation layer and the application logic. The API gateway is a façade layer that provides a consistent and uniform interface for the presentation layer to interact with, while allowing downstream services to evolve independently, without affecting the application. The API Gateway may use a technology such as APIGEE/Netflix Zuul, and allows the application to interact in a RESTful manner.
This API gateway has additional advantages other than creating a layer and separating the monolith into two small apps which can be dealt separately. Some of the benefits such as reverse proxy server which manages load balancing, SSL etc, security single point, throttling manager, service registry and discovery etc. The complete API Gateway benefits can be found here and here.
Instead of the all-in, risky battle to a cut-off date, an alternative agile approach is to observe the current application and create an application alongside the old application that gradually replaces it. Completely replacing a complex system can be a huge undertaking. Often, you will need a gradual migration to a new system, while keeping the old system to handle features that haven’t been migrated yet.
This approach provides for a progressive plan that also reframes the problem, reducing the risk that is associated with a cut-over approach. It also provides value back to the business by enabling an earlier delivery of new features and replacing old features until you have a mature application that can replace the old one. While the new feature is ready the requests should go to monolithic and once the component with new feature is ready then we can direct requests for this functionality to the newly developed component and rest of functionalities should go to monolithic only as we did not identify/implement other components. This is especially true of gradual migrations, where different features of a larger application are moved to a modern system over time.
Incrementally replace specific pieces of functionality with new applications and services. Create a façade that intercepts requests going to the backend legacy system. The façade routes these requests either to the legacy application or the new services. Existing features can be migrated to the new system gradually, and consumers can continue using the same interface, unaware that any migration has taken place.
This pattern helps to minimize risk from the migration, and spread the development effort over time. With the façade safely routing users to the correct application, you can add functionality to the new system at whatever pace you like, while ensuring the legacy application continues to function. Over time, as features are migrated to the new system, the legacy system is eventually “strangled” and is no longer necessary. Once this process is complete, the legacy system can safely be retired
If you see the figure below, Request router is the one like façade which directs the requests either to monolith/new service based on functionality
The idea is that the old and the new can coexist, giving the new system time to grow and potentially entirely replace the old system. The key benefit to this pattern, is that it supports goal of allowing for incremental migration to a new system.. Moreover, it gives us the ability to pause and even stop the migration altogether, while still taking advantage of the new system delivered so far.
A key point of this strangler application approach is not just that we can incrementally migrate new functionality to the new system, but that we can also roll back this change very easily if required. Remember, we all make mistakes — so we want techniques that allow us to not only make mistakes as cheaply as possible (hence lots of small steps), but also fix our mistakes quickly.
Every application started small and ended up to be a monolithic applications till recently until microservices architecture and advent of cloud computing changes the paradigm. With Server less which is going to be THE standard and containers, functions and steps becoming the way of development, monoliths have to shed their hefty nature and break into simpler, scalable and independent components. But this breaking is not easy and we just gone thru different strategies to identify components, integrate new and old legacy, creating layers and finally strangulating the old logic/service/functionality of monolith eventually to retire it.