From Monoliths via Self-Contained Systems to Microservices
Most code starts out as a monolith. This was my experience as well while working on a self-service application for a big telecommunication provider. Idea was to have a separate application where we will expose a lightweight REST API for our modern frontend (Angular 1 at that time). This consumer services application was supposed to be a bridge between CRM and SOAP powered web services and a lightweight client application that we developed using Angular. Without knowing full boundaries of our domain or the extent to which it will grow we started exposing REST services and it seemed like a good approach.
So what is a monolith?
A monolith contains multiple things inside one single system. Various domains and user interface, business logic, persistence. Add on top of that a lot of modules, components and libraries. Benefits of a monolith are:
- Simple to develop
- Simple to test
- Simple to deploy
- Simple to scale
- Simple to monitor
All these benefits apply at the start of the project but as we know monoliths have tendency to grow, both on the application side and the team size vise. As the time passed by we started seeing one or more of the well-known problems of this architectural approach:
- Scaling – can be seen as a benefit in the start but a problem rises as you can’t scale with an increasing data volume. Each copy of the application instance will access all the data
- Continuous deployment – in order to update a single application component an entire application needs to be deployed
- In event of increasing the team – monolith application prevents teams from working independently
- Long term commitment to technology stack
- Overloaded IDE
- Overloaded web container
- Huge code base that is hard to get to speed for new team members
So the question popups: what should we do now?
We started with a monolith, application was proving useful for everyday operations of service centers, sales and our end users. But as application and everything that goes with it was growing all the problems mentioned above start showing up. It was time to break things apart. Even if your long-term goal is microservices architecture – as we had indications of, you might want to start with self-contained systems first (SCS). And that is what we did.
So what is SCS?
- Autonomous web application (including UI, Logic and Data)
- Favors UI-level over API-level integration
- Prefers asynchronous integration
- Individually deployable
- Owned by one team
SCS says that when you do a split of your monolith you should create a vertical slice of you system. That means an SCS contains a front-end (could be a GUI and/or an API), a backend, and a persistence layer. You are not sharing code between SCS (except at the library level) and each SCS has its own database. If you want to get data from another SCS you have two choices, you can replicate data into your own database, or you can call an API belonging to that SCS. A team can own one or more SCS’s, but multiple teams cannot share an SCS. This gives your team a lot of autonomy because the only way other teams can interact with your system is through well-defined APIs or well defined replication points.
But even self-contained systems can become too big and bulky over time. Some SCS or verticals can become more and more complex and new ones are introduced. Also the need for exposing an API for third parties (resellers) rise in our case. This is when you can start thinking about breaking those down too, this time into microservices.
Microservices – also known as the microservice architecture – is an architectural style that structures an application as a collection of loosely coupled services, which implement business capabilities. The microservice architecture enables the continuous delivery/deployment of large, complex applications. It also enables an organization to evolve its technology stack.
There are many different reasons why teams might turn to microservices as an alternative architecture. For example, smaller teams might use microservices because Continuous Delivery is much easier with smaller deployment units. They can also be scaled individually instead of scaling a large deployment monolith. Large projects might use microservices to scale agile processes. Each team can autonomously develop and deploy its microservices without a lot of coordination with other teams. They can bring features into production independently. Also, almost all technical decisions can be made inside one team. Hardly any coordination is needed anymore — neither concerning technology nor concerning new features. That way much larger systems can be built because communication is often the limiting factor.
As we can see microservices resolve some of the same issues that SCS resolves as well but key differences are:
- Microservice is probably smaller unit then SCS
- There are usually fewer SCS than microservices
- SCS should ideally not communicate with each other while it is fine for microservices
- SCS has an UI
- SCS should favor integration at the UI layer while microservices usually focus on integration at logic layer
None of the three approaches is a silver bullet and all three have its place in different projects and their lifecycles. In case that you need to evolve your monolith to a microservices architecture you should consider a SCS as an intermediary step which will lead you to your goal or even prove as an end state. For us it proved much easier to break our monolith into SCS first and then evolve those that are needed into microservices then to go ahead into microservices for our entire system. Microservices are more versatile while SCS solve problems specifically with the architecture and organization of large projects. You need to figure out what suits best for your particular need. In the case I was chronologically explaining we ended up with a mix between SCS and microservices and that proved to be most suited solution for our problem. SCS handled applications that had an UI and we broke down into microservices those SCS verticals that became too big and that were required for our REST API so we can leverage the benefits such as smaller deployment units, high availability and more granulated scaling.