Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 78

Microservices: Principles and Monolith Decomposition

Recommended resources

Hello! Welcome to the first lesson in our module on Microservices with Spring Cloud.

In our previous module, you mastered the fundamentals of asynchronous and event-driven patterns using RabbitMQ. You learned how to decouple parts of an application so they can communicate without direct dependencies. This concept of decoupled communication is the lifeblood of a microservices architecture.

Today, we'll build on that foundation by addressing the learning outcome: Explain the principles of microservices and strategies for decomposing a monolith. This lesson is conceptual and foundational. We will explore:

  • What a monolithic application is and what microservices are.
  • The core principles that define a microservice architecture.
  • The key reasons why an organization might choose to migrate.
  • Proven strategies for incrementally breaking down a monolith into microservices.

Let's begin by defining our starting point: the monolith.

From Monolith to Microservices

Most applications start life as a monolith—a single, unified unit of deployment. All the application's code for handling different business functions (like user management, orders, and payments) is contained within a single codebase and deployed as one large application. You have a lot of experience with this model, as all the applications we've built so far in this course have been monoliths.

However, monoliths come in different forms. Sam Newman, a leading expert on this topic, provides a great overview of the different types of monolithic systems.

Monolith Decomposition Patterns • Sam Newman • GOTO 2019

To understand where we're going, we first need to understand where we're starting from. Let's watch this segment from Sam Newman's talk, 'Monolith Decomposition Patterns,' to explore the different kinds of monolithic architectures.

Watch the section from 02:26 to 10:32. Pay attention to his definitions of the 'single process monolith,' the 'modular monolith,' and especially the 'distributed monolith,' which he describes as a particularly problematic architecture to be in.

What are Microservices?

In contrast, a microservices architecture structures an application as a collection of small, autonomous services modeled around business domains.

This GitHub repository, which summarizes Sam Newman's book "Monolith to Microservices," provides an excellent and concise definition of their core principles.

SaraLerma/Monolith-To-Microservices

Let's review the key characteristics that define microservices.

Please read the section 'What Are Microservices?'. Focus on the four bullet points that define a microservice: independently deployable, modeled around a business domain, owning their own data, and technology agnostic.

To summarize the key differences:

Feature Monolithic Architecture Microservices Architecture
Deployment A single unit. The entire application is deployed at once. Each service can be deployed independently.
Codebase One large, shared codebase. Separate codebases for each service.
Data Storage Typically a single, shared database. Each service owns and manages its own data.
Technology Stack A single, uniform technology stack (e.g., all Java). Can use different technologies for different services.
Scalability The entire application must be scaled as a whole. Individual services can be scaled based on their needs.
Fault Isolation A failure in one component can bring down the entire app. A failure in one service can be isolated from others.

One of the primary drivers for adopting microservices is scalability. The image below, known as the "Scale Cube," illustrates how microservices enable more granular scaling.

Scaling With Microservices: The Three Dimensions of Scalability
This diagram shows three dimensions of scaling. The Y-axis (Functional Decomposition) represents breaking the monolith into distinct services based on business functions—this is the core idea of microservices.

Why Migrate? The Incremental Approach

Migrating to microservices introduces significant complexity. It's a distributed system, after all, bringing challenges with networking, data consistency, and operational overhead. So, why do it? The decision should always be driven by a clear business or technical need that the monolith cannot satisfy.

Common drivers include:

  • Improving team autonomy: Small, independent teams can own and develop services without stepping on each other's toes.
  • Reducing time to market: Services can be developed, tested, and deployed independently and more quickly.
  • Scaling cost-effectively: You only need to scale the specific services that are under high load, not the entire application.

Crucially, the migration itself should not be a "Big Bang" rewrite, where you stop all feature development to rebuild the system from scratch. This approach is incredibly risky and rarely succeeds. Instead, the universally recommended approach is incremental migration.

Monolith Decomposition Patterns • Sam Newman • GOTO 2019

In this next clip, Sam Newman emphasizes that microservices are not the goal and that the migration should be a gradual process of evolution, not revolution.

Watch from 10:32 to 14:39. Focus on the core message: avoid the 'Big Bang rewrite' and instead adopt microservices incrementally, learning as you go from deploying small changes into production.


Strategies for Decomposing a Monolith

If we are to migrate incrementally, we need safe and proven strategies to do so. Let's explore the most common patterns.

1. Identifying Service Boundaries with Domain-Driven Design (DDD)

Before you can extract a service, you must first decide what to extract. The goal is to find service boundaries that are highly cohesive (grouping related functionality) and loosely coupled (minimizing dependencies between services).

The most effective way to do this is by applying principles from Domain-Driven Design (DDD). A key concept in DDD is the Bounded Context, which represents a logical boundary within a larger business domain. For example, in an e-commerce system, "Sales," "Shipping," and "Support" could each be a Bounded Context. These contexts become excellent candidates for your first microservices.

A practical way to prioritize which service to extract first is to identify the part of your application that:

  • Changes most frequently, causing deployment bottlenecks.
  • Has unique scalability requirements that are hard to meet within the monolith.

2. The Strangler Fig Pattern

This is perhaps the most famous and widely used migration pattern. It is ideal for extracting functionality that is exposed via an external endpoint, like an HTTP API. The idea is to incrementally "strangle" the monolith by wrapping it with new services and routing traffic to them over time.

Strangler Fig Pattern for Monolith Decomposition
This image illustrates the Strangler Fig pattern. An API Gateway (or proxy) is placed in front of the monolith. Initially, it routes all traffic to the monolith. Then, as new microservices are built, the gateway is configured to route specific requests to the new services, gradually choking off traffic to the old system.

Let's hear Sam Newman explain this pattern.

Monolith Decomposition Patterns • Sam Newman • GOTO 2019

The Strangler Fig pattern provides a safe, incremental way to replace parts of a monolith. This clip explains how it works, using a proxy to redirect traffic.

Watch the segment from 14:39 to 20:47. Understand the role of the proxy and how you can deploy the new service into production safely before any traffic is sent to it.

3. Branch by Abstraction

What if the functionality you want to extract is buried deep inside the monolith, with no direct external endpoint to intercept? This is where the Branch by Abstraction pattern comes in. Since you have a background in Object-Oriented Programming, this pattern should feel familiar. It relies on creating an abstraction (like a Java interface) around the functionality you want to replace.

The process is as follows:

  1. Create an abstraction over the existing functionality (e.g., an NotificationSender interface).
  2. Refactor the monolith's code to use this new abstraction instead of calling the concrete implementation directly.
  3. Create a new implementation of the abstraction. This new implementation will call out to your new, external microservice.
  4. Use a feature flag to switch which implementation is active. You can safely deploy the code with both implementations present but only the old one active.
  5. Once you are confident the new microservice works, you flip the feature flag to direct calls to the new implementation.
  6. Finally, after a period of time, you can clean up the code by removing the old implementation and the abstraction.

Monolith Decomposition Patterns • Sam Newman • GOTO 2019

For functionality deep within the system, Branch by Abstraction is a powerful technique. Let's see how Sam Newman explains this pattern.

Watch from 20:47 to 27:58. Notice how this pattern allows two implementations to coexist in the same codebase, enabling safe, incremental refactoring and even parallel runs for verification.

Test your understanding!

You are working on a large e-commerce monolith. Your team needs to extract two pieces of functionality:

  1. The Product Search API, which is a public HTTP endpoint used by the website's frontend.
  2. The Inventory Calculation Logic, which is a set of internal classes called by many other parts of the application whenever an order is placed.

Which migration pattern (Strangler Fig or Branch by Abstraction) would be more suitable for each task, and why?

Show answer
  1. Product Search API: The Strangler Fig pattern is the ideal choice here. Because it's an external HTTP endpoint, you can place a proxy or API gateway in front of the monolith to intercept /search requests and redirect them to a new, dedicated search microservice.

  2. Inventory Calculation Logic: The Branch by Abstraction pattern is more suitable. This logic is internal and called from many places. You would create an InventoryCalculator interface, refactor the existing code to use it, and then create a new implementation that calls out to an inventory microservice. A feature flag would allow you to switch between the old internal logic and the new service-based logic without disrupting the system.


Conclusion

In this lesson, we've laid the conceptual groundwork for our journey into microservices. You now have a solid understanding of the principles, the trade-offs, and the key strategies involved in moving from a monolithic architecture to microservices.

Key Takeaways:

  • Microservices are small, autonomous, independently deployable services modeled around business domains, each owning its own data.
  • The primary drivers for adopting microservices are often non-functional requirements like scalability, team autonomy, and speed of delivery.
  • Migration should always be incremental. Avoid the "Big Bang" rewrite.
  • The Strangler Fig pattern is used to replace externally-facing functionality by intercepting traffic with a proxy.
  • The Branch by Abstraction pattern is used to replace deeply embedded internal functionality by creating an abstraction layer and switching implementations.
  • Defining service boundaries and decomposing the database are two of the most significant challenges in any migration.

What's Next?

Now that we have the foundational theory, it's time to get hands-on. A key challenge in a microservices ecosystem is managing configuration across many different services. In our next lesson, we will begin building our microservices landscape by tackling this problem head-on. You will learn how to implement a centralized configuration server using Spring Cloud Config. This will be our first practical step in creating a robust and manageable microservices architecture.