Determining service boundaries and decomposing your monolith

Published by Emre Baran on November 04, 2024
Determining service boundaries and decomposing your monolith

Transitioning from a monolithic architecture to microservices is an intricate, time-consuming task. It demands both strategic foresight and meticulous execution.

In this 10-part series, we’ll guide you through the most common challenges faced during monolith to microservices migration. In the first part, we’ll start with the pivotal task of decomposing your app and defining service boundaries.

We'll publish a new article every Monday, so stay tuned. You can also download the full 10-part series to guide your monolith-to-microservices migration.

“Despite the level of effort and cost to migrate to microservices, the benefits far outweigh the cons, especially because you will never need to replatform again.” - Thoughts from Ryan Bartley, ex-eBay, Co-founder at Canopy

Service boundaries, cohesion, and decomposition

Your first major hurdle when decomposing a monolith application is defining appropriate boundaries for each microservice. Creating the right boundaries will lay a robust foundation for your microservice transition without creating excess complexity. The process involves breaking down a monolithic application into smaller, independently deployable services that align with business capabilities or domains.

Your goals at this stage are to:

  • Define boundaries between microservices by understanding how services align with business capabilities.
  • Define relationships between microservices by balancing cohesion within a service with loose coupling. Cohesion refers to the degree to which the components within a service are related and focused on a single responsibility. A highly cohesive service encapsulates related functionalities and data, making it easier to understand, develop, and maintain. On the other hand, loose coupling ensures that services can be developed, deployed, and scaled independently, without tight dependencies on other services.

If services are too coarse-grained—with multiple responsibilities and tight coupling—they can become what is commonly known as a “distributed monolith”. This effectively negates the benefits of microservices.

Conversely, if services are too fine-grained, with overly narrow responsibilities, it can lead to increased complexity, performance overhead, and difficulties in data consistency and transaction management.

To strike the right balance, you should start with a domain analysis. Once you’ve identified the core business capabilities, you can gradually extract services based on them. Techniques like event storming, domain storytelling, and use case analysis will also help you on the way.

How to define boundaries between microservices

To identify the right service boundaries, use the following principles: Domain-Driven Design and Single Responsibility Principle.

How to define boundaries between microservices.png

Domain-Driven Design (DDD)

In DDD there’s the concept of bounded context that is helpful in defining boundaries between microservices. Each bounded context represents a specific domain or subdomain within the business that contains its own ubiquitous language, domain model, and set of business rules.

“DDD is solving a complex problem by usually breaking the problem into smaller parts and focusing on those smaller problems that are relatively easy. A complex domain may contain sub domains. And some of sub domains can combine and group with each other for common rules and responsibilities”, explains Mehmet Ozkaya, ex-Ericsson Software Solutions Architect and Udemy course creator.

During decomposition, it’s helpful to think of each service as a bounded context. Any microservice that responds to that bounded context becomes part of that service’s domain. So, by mapping your services to bounded contexts, you ensure each service has a clear and focused responsibility aligned with the business domain.

Single Responsibility Principle (SRP)

SRP is the first of five principles in the SOLID approach to object-oriented design. SRP states that a class or module should have only one reason to change. When you apply SRP to monolith decomposition, it guides the process to constrain each service to a single, well-defined responsibility. All related data and functionalities are then encapsulated within that domain.

Finding the right balance

Decomposing a monolith application into microservices is not a simple or straightforward process. Doing it well requires an iterative approach, with continuous refactoring and evolution.

Here is some good advice from Eldad Palachi, Principal Architect at vFunction on how to get started:

"Start with functionality that is already somewhat decoupled from the monolith, does not require changes to client-facing applications, and does not use a data store. Convert this to a microservice. This helps the team upskill and set up the minimum DevOps architecture to build and deploy the microservice."

Now, let’s take a look at how companies decompose a monolith in real life.

How Netflix decomposed their monolith into microservices

Through an iterative process, Netflix successfully decomposed their monolithic application into microservices using DDD and SRP principles.

Utilizing bounded contexts

First, Netflix identified key bounded contexts within their business domain, like user management, content catalog, recommendations, and playback. Then, each bounded context was mapped to a set of microservices responsible for that specific domain.

For example, the user management bounded context was decomposed into services like:

  • user authentication
  • user profile
  • user preferences

These services encapsulated the related functionalities and data, ensuring high cohesion within each service.

Implementing SRP

Netflix also applied SRP to their microservices design. So, each service had a clear and focused responsibility, such as handling user authentication, managing the content catalog, or providing personalized recommendations. This approach allowed Netflix to develop, deploy, and scale services independently, which promoted loose coupling and greater flexibility.

Optimizing with patterns

To handle complex business transactions and ensure data consistency, Netflix used patterns like event sourcing and CQRS (Command Query Responsibility Segregation).

Event sourcing allowed them to capture all changes to a service's state as a sequence of events. This provided a complete audit trail and enabled event-driven architectures. CQRS separated the read and write responsibilities of a service, which optimized performance and scalability.

Gradually improving architecture

Netflix gained insights into service performance after decomposition through monitoring, logging, and tracing. Using this data, they identified improvement opportunities and continually refined their service boundaries. Over time, they optimized their microservices to maintain alignment with business domains.

How Netflix benefited from decomposition

Applying domain-driven design, SRP, and continuous refactoring, Netflix decomposed its monolith into cohesive and loosely coupled microservices. This allowed them to scale their platform, accelerate development, and deliver a seamless streaming experience.

Looking ahead

Ready for the next in the series? Continue to how to decentralize data management in a consistent manner when migrating to microservices.

Or, you can download the complete 10-part series in one e-book now: "Monolith to microservices migration: 10 critical challenges to consider"

Book a free Policy Workshop to discuss your requirements and get your first policy written by the Cerbos team