Approaches to splitting the monolith - MSA and alternatives over the years
by Tyukavkina Ekaterina (entiukavkina@edu.hse.ru)
Introduction
A monolithic architecture is a traditional model of a software program, which is built as a unified unit that is self-contained and independent from other applications. The word “monolith” is often attributed to something large, which is not far from the truth of a monolith architecture for software design.
А monolithic architecture is a singular, large computing network with one code base that couples all of the business concerns together. To make a change to this sort of application requires updating the entire technology stack by refactoring the code base and building and deploying an updated version of the service-side interface. This makes updates restrictive and time-consuming [2].
Monolithic architecture applications are common in the information technology industry because of the simplicity of its development but also because “many companies have already got running monolithic systems which are still great value in business”.
Over time the monolith tends to increase its complexity making it hard for the developers to fully understand the system thus slowing development cycles and consequently the delivery of value to the clients. The increase of complexity will also make the components of the system easily become tightly coupled, hindering the evolvability of the code, and the reliability of the application may also decrease since one bug can turn the application unavailable [1].
Service Oriented Architecture
When the number of in-house custom applications started exploding and there was a need for data standardization, a common language to describe enterprise objects and de-coupled services with standards requests and responses.
Some organizations like IBM started developing their own XML based engines around message queues and JMS standards while others adopted the early service bus products from vendors. Thus Service-Oriented Architecture (SOA) was born with lofty goals to build canonical enterprise data models, reduce point-point services (Java applications had a build-time dependency to services they consumed, other Java services), add standardized security, build service registry [3].
SOA is an architectural style that focuses on creating and organizing software systems as a collection of loosely connected and interoperable services. In SOA, each service represents a specific business function or piece of functionality and may be developed, deployed, and maintained independently. These services communicate with one another via well-defined interfaces, generally utilising standardised protocols like HTTP or SOAP.
Here are five prevalent design patterns used in Service-Oriented Architectures (SOA):
- Service Registry: This is a database that acts as a repository of services, where service providers can publish their services and consumers can look them up. It simplifies the process of finding and invoking services in a distributed environment.
- Enterprise Service Bus (ESB): This acts as a communication center that manages communication and message routing between services. It can decouple systems from each other, and provide additional services like message transformation, routing, and applying business rules.
- Service Facade: This pattern provides a unified, higher-level interface to a set of interfaces in a subsystem. It helps to reduce the complexity of interactions between services and enables loose coupling.
- Message Bus: This pattern allows general communication between components without requiring components to have knowledge of the system they are part of. The key idea is to introduce a third party (message bus) to deliver messages to the components.
- Composite Services (Orchestrated Choreography): This design pattern enables you to assemble coarse-grained services from finer-grained services. It’s a way of creating a workflow of services where you coordinate or compose services to create a service providing higher-level functionality [5].
Microservices architecture
Microservices Architecture (MSA) was first introduced in 2011 at a software engineering workshop in Venice. This type of architecture evolved from SOA, but unlike it, they allow to develop applications that are a set of services, rather than a single monolithic code. Each of them is independent software with its own functionality and database. This approach simplifies application modification and maintenance. It is assumed that refactoring and redeployment of several services will be enough, rather than redesigning and releasing a new version of the monolith.
This is a much simpler architecture than SOA. Today, SOAP has been largely replaced by the REST protocol, which works with a simpler data exchange standard, JSON. Microservices present itself as a solution to solve some of the problems presented by the MA. Lewis and Fowler define MSA “as an approach to developing a single application as a suite of small services, each running in its process and communicating with lightweight mechanisms, often an HTTP resource API”. By breaking the application on a set of services the deployment process becomes easier because code changes only require the deployment of the corresponding service, reducing the complexity making it easier to understand the code and consequently to develop and maintain it. By reducing the complexity, the development cycles become faster turning the continuous integration also easier. Independent services enable the independent scaling and better implementation of the agile process, for instance, a company can have a set of teams working independently with their development cycles and technologies of their choice [1].
Here are five famous design patterns used in microservices:
- API Gateway: In a microservice architecture, clients often need data from multiple services. Instead of making calls directly to each microservice, the client makes a few consolidated calls to an API Gateway, which then orchestrates the microservices required to handle the request. This pattern simplifies the client-side code and reduces the number of round-trip calls between client and server.
- Circuit Breaker: Given that microservices often depend on each other, the failure of a single microservice can cascade to other services. The circuit breaker pattern helps prevent this. When a network call from a service fails repeatedly, the circuit breaker trips, and for a subsequent period, all attempts to invoke the service will fail immediately. After a timeout, it allows a limited number of test requests to pass through. If these requests succeed, the circuit breaker resumes normal operation; otherwise, the timeout period begins again.
- Saga: In a microservices architecture, achieving data consistency across services can be challenging. The Saga design pattern is a way to manage data consistency across services in a microservice architecture. Sagas are a sequence of local transactions where each transaction updates data within a single service. If one transaction fails because it violates a business rule, then the saga executes compensating transactions to undo the impact of the preceding transactions.
- Service Discovery: Given that microservices run on different machines or containers, it’s important for services to be able to find and communicate with each other. The service discovery pattern provides a database for microservices to register their locations and enables other services to locate them.
- Event-Driven Architecture: Microservices often need to communicate with each other to stay in sync. Instead of direct communication, services can asynchronously publish events that represent state changes. Other services can then subscribe to these events and update their own state accordingly. This approach decouples services and provides a natural way to maintain consistency across services in a distributed environment [5].
Conclusion
Monolithic approach may be one of the best approaches for smaller applications that are not expected to grow. In applications where maintenance and scalability is irrelevant, the monolith can be a more optimal choice.
It is not always possible to predict at the start of the project that the project will not grow. Many projects have the ambition to have only small changes and fixes and their scaling and growth are not considered. Later on, the need to scale appears and the project's architecture is already a large-scale complex monolith. This happens very often as the future is always unpredictable and it is impossible to read the minds of all possible potential customers. As the monolith grows it brings problems with maintainability and scalability to the point that the system is almost unable to efficiently evolve under current architecture. And in this case, an option to migrate to microservices might be a solution.
However, its disadvantages should be carefully considered, incorrectly implemented microservices can have fatal impacts. For instance, if a service built with different technologies is maintained only by one person who later leaves the company and the person is the only one in the company who can work with the technologies used in the service, it becomes impossible to maintain or extend the service.
References
- From Monolith to Microservices: automating service boundary detection, Rúben Xavier Cruz de Jesus, 2021 - https://repositorio-aberto.up.pt/bitstream/10216/137554/2/513284.pdf
- From a Monolithic to a Microservices architecture, Lu´ıs Fernando Ambrosio Nunes, 2018 – https://d1wqtxts1xzle7.cloudfront.net/90942284/77940-Dissertation-libre.pdf
- History of web services: Monolith to Microservices, 2020 – https://alok-mishra.com/2020/02/26/how-did-we-get-to-microservices/
- From Monolith to Microservices: Refactoring Patterns, BJ Široký, 2022 - https://is.muni.cz/th/f8zv4/Master_Thesis_Archive.pdf
- Monolithic vs. Service-Oriented vs. Microservice Architecture: Top Architectural Design Patterns, 2014 – https://www.designgurus.io/blog/monolithic-service-oriented-microservice-architecture