How strongly do I recommend Monolith to Microservices?
9 / 10
What a great read. In Monolith to Microservices, the first lesson you learn is that microservices are not the goal. In fact the author cautions you against going the microservice route because of the additional complexity.
However, should you implement a microservice architecture, this book provides you with a guide full of incremental steps to take and potential problems you will encounter along the way.
Top Ideas in This Book
Good things happen when you can deploy a service without needing to deploy another. As an example: CI/CD systems get faster, providing tighter feedback loops for developers, which encourages smaller and more frequent deployments.
The opposite happens when you find developers orchestrating their deployments with other services or potentially other teams. Communication increases, deployments become less frequent, operations are batched in an attempt to minimize communication, and risk builds.
Moving to a microservice architecture is not an all-or-nothing proposition. Start small and only build the number of services that your team(s) can actually handle.
In theory, microservices function in granular, narrow spaces. In practice, your team needs to decide where those boundaries are and be cautious to spin up more services than your team is capable of handling. Err on the side of fewer services which are organized in a way that supports future decomposition.
Monolith is a dirty word for many engineers. However, monoliths are a valid architecture choice – particularly for startups who are still trying to figure out the domain and arrive at product market fit.
In these volatile environments where the product concept is still being figured out, a monolith will help the team move faster because code is easier to move around and there is less infrastructure to change.
Conversely if your startup spins up many microservices, you are likely to create the wrong boundaries because you don’t yet understand customer problems well enough. Your understanding of the domain is insufficient to be accurately reflected in your services structure.
In my experience, I’ve found that using the phrase “modular monolith” softens the blow for many engineers who have an adverse reaction to the term “monolith.”
Using microservices is an architecture option, not a goal unto itself. Customers don’t care whether you have a microservice architecture or not. The goal should be something else like establishing clearer boundaries between teams, increasing productivity, reducing cognitive load of developers, isolating specific problems, etc.
Consider the Build-Measure-Learn feedback loop used by lean startups. Start with a single service. Build it. Measure how it performs against your expectations – does it deliver the organizational benefits you hoped for? If not, why? Learn from that experience and build the next service, apply your new knowledge.
In the Build-Measure-Learn approach, your services will incrementally improve with each iteration. You will minimize risk because when your service design or implementation contains a flaw, that flaw is likely to be contained to just that service not the whole bunch.
What customer problems are you trying to solve? Diagram those problem spaces out to identify boundaries between services. The better you understand your domain problem space, the more closely your service structure will match the problem, thereby creating less confusion and more clarity for your developers.
This is a spin on an idea presented in Clean Architecture – your code structure should match system intent. If you’re building an app for exercise, your code and your services should look like they belong to an exercise app, not a generic CRUD app.
When building new services, you will be tempted to push logic up into middleware where it can be leveraged by multiple services. In theory this provides reusability and deduplication. But in practice you are likely to slow down feature development
Apply middleware logic judiciously and consider if that logic should belong in its own service rather than in middleware.
Most teams transitioning from monolith to microservices start with the code. Many teams unfortunately stop there, leaving a shared database untouched.
By splitting out the code but not data, we have made the code more complex while not deriving the benefits of separate services. We are still at the mercy of a single point of failure where multiple teams and services must still communicate heavily to avoid stepping on each other’s toes.
The harder parts about splitting out the data are usually:
The difficulty of splitting data sources is another reason to judiciously separate services, not go all-in at once. Take your data migrations one at a time.
Spotify uses a modular monolith, where modules encapsulate a single problem domain. These modules can be independently managed by the appropriate team. When your code follows a modular monolith architecture, you have options along the way of deciding which modules to split out into separate services.
Service oriented architectures require services to communicate through APIs. Sometimes those APIs can be REST endpoints, other times they are things like event buses. In any case, it’s important that your API contracts remain stable and predictable for consumers to avoid breakage.
Trouble happens when API endpoints are built on magic code, for instance a data model that gets automatically decoded to JSON for external consumption. You may change some internal on the data model and not realize the downstream effects on consumers. Instead of relying on magic, be more explicit about what fields and data gets returned for consumption in each of your API endpoints.