Books for software engineers and managers

Monolith to Microservices

Monolith to Microservices

Evolutionary Patterns to Transform Your  Monolith

by Sam Newman

Tech Lead,
Star Engineer

How strongly do I recommend Monolith to Microservices?
9 / 10

Review of Monolith to Microservices

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.

Embrace independent deployability

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.

When you start, focus on how many microservices you can handle and how you define boundaries to avoid service  coupling

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 unfortunately synonymous with legacy for many people, even though monoliths are a valid architecture  choices

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.”

Microservices are not the  goal

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.

Break apart monoliths one step at a time, not in a big  bang

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.

Map your domain model to understand service  boundaries

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.

Keep the middleware pipes dumb, the endpoints  smart

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.

Do not use a shared database accessed by separate services, split out the data as  well

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:

  1. Updating logic in code, particularly SQL queries that join multiple tables across what should be separate databases. To do this correctly, you will need to make multiple separate service calls in place of what is often a single SQL query. Moving from one SQL query to several service calls will negatively impact the performance of your app, but by taking the microservices path you are acknowledging that tradeoff because it comes with the benefit of clearer separation of concerns and increased scalability – both for the system and your teams.
  2. Decomposing tables. Commonly used tables like User might encompass five or six concepts that need decomposition into separate data stores. These seams can be confusing and difficult to decide on when multiple domains depend on the same data.
  3. Data migrations are hard, particularly at larger scales. You might require maintenance modes, downtime, and clearer backup or disaster recovery plans.

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.

Modular monoliths are a great alternative to  microservices

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.

Make endpoint contract changes very obvious to API publishers by avoiding  magic

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.

Monolith to Microservices