Whilst breaking a large codebase into a series of smaller services seems logical, the reality is far from simple. You have to ask yourself if the overhead and complexity of moving to microservices is worth your team's time or if you could extract more value from your existing monolith.
The Rise of Microservices
Microservices is an architectural pattern where a system is built up of a number of smaller, deployable services that communicate and interact with each other to provide the behaviour and functionality of the overall system. Each service is designed to be entirely independent of one another. The contrasting opposite to this is what is a "monolith". A monolith is a single deployable application that encompasses all the behaviour and functionality of the system.
Microservices first emerged in workshops and conference talks in 2011 and 2012. At the time, the concept didn't have a unified name. Martin Fowler can take credit for introducing the internet to the term microservices in the "Microservices – a definition of this new architectural term" article that he co-authored with James Lewis.
I first discovered microservices towards the end of 2015 and quickly bought a copy of Sam Newman's "Building Microservices: Designing Fine-Grained Systems". This book is a must-have, easy-to-read resource for anyone looking to clue themselves up on the topic. Although this is the first edition, now eight years old, you may be better served by getting the revised second edition published in 2021.
Back in 2014-2015, microservices were a new concept. Whilst there are examples of some organisations using microservices before then, they weren't necessarily labelled as microservices. For example, amazon.com started moving towards fine-grained services (what we would now call microservices) in the early-mid 2000s. The work of people like Sam Newman and others brought the idea of microservices into the spotlight.
Over the years, microservices have become much more popular, almost to the point where some in our industry use microservices as a starting point because "that's what everyone else is doing".
Why Are Microservices Popular?
Microservices have been wildly adopted by larger tech organisations such as Amazon, Netflix and Uber, which has undoubtedly influenced the popularity of microservices.
Influence aside, some of the more notable benefits of microservices are as follows:
Each microservice can be scaled independently of one another, meaning that you don't have to scale the entire application if one particular component of the system is under high load.
- Independence and Team Autonomy
Microservices are designed to be entirely independent, which means that a team responsible for a microservice should be able to realise and deploy their service without depending on another team.
- Technology Diversity
Because each microservice is independent, the team building the service has the freedom and flexibility to pick the technology stack best suited to their service's domain and problem space.
- Fault Tolerance
With a microservices architecture, a fault can be isolated to a given microservice, making it easier to achieve fault tolerance and graceful degradation in the event of a failure.
Microservices are often built to be modular and reusable, with each microservice having a specific purpose or responsibility within the wider ecosystem. Whilst a microservice may have been written with particular consumer(s) in mind, it's easy for a new service to consume the functionality of an existing microservice if such a need arises.
Ultimately, microservices allow teams to make choices and decisions at a smaller level than is typically afforded by a monolith; as a result, decisions can become more focused on the specific context of the microservice in question.
Monoliths can also become large and unwieldy. Therefore, the prospect of breaking down a monolith into smaller applications can sound appealing in its own right.
For any team wanting to adopt microservices, the desire can be to jump in and start wrestling with the technical challenges involved in adopting a microservices architecture. However, before this happens, it is worth recognising and understanding the impact that Conway's Law can have on system design.
Conway's Law is an adage that states that the structure of a system is heavily influenced by the communication structure of the organization that produces it. This is often illustrated through the following scenario:
If you have four teams working on a compiler, you'll get a 4-pass compiler
Put simply, the structure of an organisation will influence the structure and communication channels built into the system that the organisation is designing. This means that the nature and quality of an organisation's microservices architecture will be linked with the organisation structure. As a result, microservices aren't just a software architecture pattern but also an organisational pattern.
When Amazon was considering splitting up amazon.com into fine-grained services, this was partly to solve organisational issues, where teams would struggle to ship features because everyone was working on the same (monolithic) codebase. Coordinating the changes to a monolithic codebase was becoming incredibly expensive. Their solution was to move to what we would now describe as microservices:
In the fine grained services approach that we use at Amazon, services do not only represent a software structure but also the organizational structure. The services have a strong ownership model, which combined with the small team size is intended to make it very easy to innovate. In some sense you can see these services as small startups within the walls of a bigger company.
This is a quote from "Working Backwards" on Werner Vogels, All Things Distributed blog.
Building a microservices architecture ultimately involves building a distributed system. Microservices, therefore, inherit all the complexities of a distributed system, and consideration has to be given to factors such as network latency, data consistency, and fault tolerance. This ultimately results in complexity that has to be built into the overall system.
Imagine a small engineering team that wants to increase its productivity and move away from a monolith in favour of smaller services which are modular and independent. As a result, they start to consider a microservices architecture. Whilst microservices will be able to deliver these benefits, the team will also have to grapple with the additional complexity of distributed systems which ultimately comes with a microservices architecture which quickly offsets and detract from these benefits the team were looking to deliver.
After all, small teams are usually resource-constrained enough without having to deal with additional overheads. In a larger engineering team, the overheads of microservices and distributed systems are much more easily absorbed, in part because larger teams have more resourcing capacity and also because the benefits of microservices and distributed systems often increase with the size of the system/teams.
Microservices combine and build upon a number of existing concepts, such as continuous delivery, infrastructure automation and domain-driven design.
Bounded Context is a concept from domain-driven design that can be used to help manage the complexity of a system. Systems typically have different areas of responsibility. And, whilst an entire system needs to inter-communicate and exchange information, the nature of the information varies depending on whether the activity is internally focused within a given area of responsibility or is communicating between different areas.
Each area of responsibility should have a clearly defined boundary and is considered a bounded context, meaning that any given domain consists of multiple bounded contexts. Interaction between bounded contexts should be done via well-defined interfaces that control the nature of the information that can flow in or out of any given bounded context.
Within a bounded context, models and entities can be optimised internally to meet the needs of the given context and area of responsibility. The interface definition of a bounded context will control which information is communicated externally to other contexts.
Bounded Contexts help to deliver a scalable and modular architecture where responsibilities within an application are well structured, and communication happens over well-defined interfaces. When used correctly, Bounded Contexts can increase a system's clarity, maintainability, and scalability. Structuring an application well, with clearly defined boundaries and areas of responsibility, allows for areas of the system to be worked on independently and even by different teams, provided the boundaries and interfaces are maintained.
Domain-driven design and bounded context can deliver many of the benefits that microservices promise without the complexity of splitting a monolith into discrete services and inheriting the complexities of a distributed system.
A system's architecture is often seen as fixed, static or infrequently changing, which can be dangerous considering that there is no silver bullet when it comes to the design and architecture of any system.
Microservices can offer a tremendous benefit when they are adopted at the right point in time. However, if microservices are adopted prematurely, they can often add unnecessary overheads that detract from the benefits that microservices aim to deliver.
In most cases, microservices are prematurely picked as a solution to dealing with modularity and complexity in systems maintained by organisations that don't have the organisational challenges or scale that microservices can assist with.
Instead, these organisations would be better served by better adopting domain-driven design and sticking with the monolith that they have. Domain-driven design done well will also act as a stepping stone towards microservices.
When the organisation and system reach the size, complexity and challenges that truly justify microservices, a system that makes good use of domain-driven design should already be primed to be split into microservices, as functionality should already be split into clean areas of responsibility, with clear interfaces at each boundary.
The architecture of any system should ideally be evolutionary, focusing on ensuring that the architecture can respond to changing requirements, technologies, and business needs without requiring significant rework or redesign, and jumping straight from a monolith to microservices often results in a lot of rework and redesign.
The architecture of any system will often go through numerous evolutions over its lifetime. Microservices is likely one of those evolutions, but the question is if you're at the right stepping stone on this evolutionary path for microservices.
There are many different ways to approach the challenges that can come with a monolithic architecture – microservices is just one approach.
Any well-architected system should always be evolving; microservices can often be treated as a destination everyone should arrive at. Instead, microservices are a very valid step on an evolutionary path that will serve some organisations and systems well, whereas others will get more benefit out of a well-architected system that follows the concepts of domain-driven design and defers the decision to adopt microservices until both technical and organisation needs warrant it.
As Sam Newman says in the introduction to his book, microservices are no silver bullet:
No Silver Bullet
Before we finish, I should call out that microservices are no free lunch or silvet bullet, and make for a bad choice as a golden hammer.