When it comes to working on a greenfield project, we see countless anecdotes online that suggest it is to start with a monolith. However, based on several experiences and insights, it has become evident that like most architecture areas (and essentially anything that is subjective), starting with a monolith may not always be beneficial. It all depends on the context.
Why starting with a monolith has been generally advocated?
1. To come up with good microservices architecture, first, you need to identify the boundaries where the system should be split up. These boundaries may not be seem right at the outset for a new system. But, what you need to do when you are looking to identify these independent services is to find the right set of “Bounded Contexts” in the problem domain. Bounded Contexts are sub-systems which individually have a unified conceptual model (which may not exist at the level of the overall system). Finding these bounded contexts requires a certain level of understanding and familiarity with the domain. The team may lack the requisite domain knowledge to be able to draw the right boundaries between the contexts.
This is not the only criteria utilized to split up the system though. There may be a case where a specific part of the system needs to scale in a very different way - like a sub-system that performs large computations and is highly CPU-intensive. It would be an excellent idea to carve-out such a sub-system as a separate microservice so that it can be scaled independently.
2. Microservices bring in their own set of complexities and can lead to greater effort and risk at the starting of the project. When you build a more distributed system, with independent components talking to each other over an unreliable network rather than a simple API call within a single process, there is a lot that can go wrong.
You need to find answers to important questions such as how to handle the communication between various services, how to manage transactions across data spread over multiple services and databases, how to deal with the problem at a granular level when exposing APIs, and how the services discover each other in a dynamic environment where instances are frequently coming up and going down. These are among other such considerations which are different and more complicated from a traditional application development standpoint.
It is also an absolute must that you have an appropriate DevOps practice in place before you can even consider development using microservices. The operational complexity that a microservices architecture brings in is much higher than managing deployments and monitoring a single all-inclusive application.
3. Dealing with cross-cutting concerns across each service is another aspects to consider. With a monolithic application, this is a one-time effort for the overall application where you put in mechanisms or build a framework to deal with things like logging, configuration, monitoring, etc. However, with microservices, you need to do it for each service.
All the above are valid considerations, however there is another side to the argument which is often overlooked. It is difficult to split up a monolith once it already exists. Even if sub-systems and components are envisioned, some level of coupling is bound to come in during implementation. When you split up the application at a later stage, there is an additional effort involved to refactor/re-design the application and replace the in-process calls with different communication paradigms (REST calls or event passing). You will most likely have a standard set of domain objects which you now need to somehow split across the services. Additionally, you may now wish to use a different technology for a component as it is better suited to the task (which is an option you get with microservices), but it requires an expensive re-write of that component.
So, while there is possibility to reduce risk and save efforts when starting with a monolith (there are arguments below on how these can be managed to start with microservices), you end up paying a heavy penalty when you eventually decide to move to microservices. (Of course, your system may not be complex enough to warrant microservices at all, but that’s not the scenario in question here).
When is it a good idea to start with Microservices?
Based on our experiences at Nagarro, here are some pre-requisites when starting with microservices can be good:
- Domain experience: If there are people in the team (especially business analysts) with domain experience, they can help the team identify the sub-domain boundaries. Another important aspect is a thorough analysis of the business requirements and clarity on the vision of the system to be built. With an understanding of the system vision and drawing on the existing experience in the domain, the team can do a great job at splitting the system and coming up with a good set of microservices to start with. There may still be a need for future revisions, and some of the services may need to be further split or merged as the application evolves, but the team would have a good starting point for setting-up a microservices ecosystem and infrastructure.
- Experience of working with microservices: Existing expertise in building and operating microservices allows a team to start quickly and deal with the operational aspects and complexity that microservices bring to the table. Knowledge and experience of distributed data management using patterns such as Saga, CQRS, and event-sourcing helps the team make the right choices for handling queries and transactions. An API gateway is often employed to handle aspects like security and API composition. Patterns for operational aspects like service discovery and circuit breaker help build a more reliable and extensible system.
When it comes to deployment and operations, it is essential to be aware of containers, orchestration platforms like Kubernetes, and various offerings available on the different cloud platforms. Having worked with these technologies and setting up a good monitoring solution using ELK or Prometheus gives the team the confidence to operate a system based on multiple services and resolve issues when things go wrong.
- Frameworks/accelerators to reduce efforts: Common cross-cutting concerns can be dealt with using accelerators (built based on experience and best practices) which help teams hit the ground running and save efforts defining common aspects and infrastructure components.
These pre-requisites have helped Nagarro successfully execute projects starting directly with a microservices architecture. Though not all projects should use microservices and in some cases monolith-first might still be better, having the pre-requisites in place allows us to have the choice to start with microservices when it doesn't make sense in a project, and save the time and effort later to split things when it would be more difficult.
Additionally, microservices opens the possibility of building a system using multiple programming languages and technologies – the best fit for each service. This opportunity is seldom practically realized as it is difficult to put together a team with varied skill-sets. Having resources with multiple and diverse skill-sets puts Nagarro in a great position to leverage this possibility and to realize the full potential of such an architecture.