Chapter 2: Decomposition Strategies

How to define your application's microservice architecture — from understanding software architecture, through decomposition patterns, to defining service APIs.

6 Patterns 17 Definitions 6 Principles 5 Problems 5 Tradeoffs 2 Technologies 41 Total Concepts

Table of Contents

  1. 2.1 What is the microservice architecture exactly?
    1. What is software architecture and why does it matter?
    2. Overview of architectural styles
    3. The microservice architecture is an architectural style
  2. 2.2 Defining an application's microservice architecture
    1. Identifying the system operations
    2. Defining services by applying the Decompose by business capability pattern
    3. Defining services by applying the Decompose by sub-domain pattern
    4. Decomposition guidelines
    5. Obstacles to decomposing an application into services
    6. Defining service APIs
  3. Key Takeaways

2.1 What is the microservice architecture exactly?

What is software architecture and why does it matter?

Software architecture is the decomposition of a system into parts (elements) plus the relationships between those parts. This definition comes from Bass and colleagues. The way you decompose your system determines its quality attributes — the so-called "-ilities."

Quality Attributes (-ilities): Architecture determines maintainability, testability, deployability, scalability, and reliability. These properties matter far more than functional requirements when choosing an architecture.

The 4+1 View Model

An application's architecture can be described from multiple views. The 4+1 view model defines four views, each showing a different aspect:

View Focus Example in Microservices
Logical How developers organize the code Hexagonal Architecture (Ports & Adapters)
Implementation Build output — deployable units Monolithic Architecture or Microservice Architecture
Process Running components and their communication Interprocess Communication (IPC)
Deployment How processes map to machines Infrastructure (containers, VMs, cloud)

The "+1" is the set of scenarios (use cases) that drive the architecture. Each view answers a different question, and together they give a complete picture.

Architectural Style

An architectural style, as defined by Garlan and Shaw, is a vocabulary of components and connectors plus a set of constraints on how they can be combined. Think of it as a template for organizing a system. The same application can use different styles for different views.

Analogy: An architectural style is like a building blueprint template. A "colonial style" house has specific rules about shape, roof, and columns. Similarly, a "hexagonal style" application has specific rules about how business logic connects to the outside world.

Overview of architectural styles

Layered Architecture (Logical View)

The traditional layered (three-tier) architecture organizes code into layers: presentation, business logic, and persistence. Each layer can only depend on the layer directly below it.

This style has several drawbacks:

These drawbacks motivate a better alternative: the Hexagonal Architecture.

Hexagonal Architecture (Ports & Adapters)

In Hexagonal Architecture, the business logic sits at the center. It does not depend on anything external. Instead, it defines ports — interfaces for communicating with the outside world. Adapters connect external systems (web, database, messaging) to those ports.

Key Point: Adapters depend on the business logic, not the other way around. This keeps business logic independent and easy to test.

Note: Hexagonal Architecture is the recommended internal architecture for each individual microservice.

Monolithic vs. Microservice Architecture (Implementation View)

The implementation view decides how the application is packaged and deployed:

Style Structure Deployment
Monolithic Single deployable unit One process or artifact
Microservice Set of independently deployable services Each service runs in its own process

These two styles are alternatives. You choose one or the other for the implementation view.

The microservice architecture is an architectural style

The microservice architecture is a specific architectural style for the implementation view. It structures the application as a set of services (components) that communicate through protocols (connectors).

Each service internally uses Hexagonal Architecture for its logical view. So the implementation view (set of services) and the logical view (hexagonal design inside each service) work together.

What is a service?

A service is a standalone, independently deployable software component. It has:

Loose Coupling

Services interact only through their APIs. They never share databases or internal data structures. This loose coupling means you can change one service without changing others.

Warning: If two services share a database, they become tightly coupled. A change to the database schema can break both services. Always avoid shared databases.

Database per Service

Each service has its own private database (or schema). This gives two benefits:

Shared Libraries

It is fine to share libraries for stable, general-purpose utilities (e.g., logging, string manipulation). But business logic should never go into a shared library. If business logic changes, you would need to redeploy every service that uses the library. Put business logic in a service instead.

Service Size

The "micro" in microservice is misleading. The size of a service (lines of code, number of endpoints) is an irrelevant metric. What matters is that a small team can own the service independently, with minimal coordination with other teams.

Analogy: Think of a service like a department in a company. A department's "size" does not matter — what matters is that it has clear responsibilities and can do its work without constant meetings with every other department.

2.2 Defining an application's microservice architecture

Defining a microservice architecture is a creative, iterative process — not a mechanical procedure. It follows three main steps:

  1. Step 1: Start with requirements. Build a high-level domain model (nouns) and identify system operations (verbs) with their pre- and post-conditions.
  2. Step 2: Decompose the application into services using one of two patterns: Decompose by Business Capability or Decompose by Subdomain.
  3. Step 3: Assign operations to services, determine which services must collaborate, and define each service's API.

Note: These steps are not strictly sequential. You will go back and forth as you learn more about the domain.

Identifying the system operations

The first step creates two outputs from the application requirements:

Each system operation has:

System operations are categorized as commands (create, update, delete data) or queries (read data).

Defining services by applying the Decompose by business capability pattern

A business capability is something a business does to generate value. It describes what the business does, not how it does it. Business capabilities are identified by analyzing the organization's purpose and structure.

Pattern: Decompose by Business Capability

Problem: How do you decide which services to create?
Solution: Define services that correspond to business capabilities.
Benefit: Business capabilities are stable (the "what" changes rarely), so the resulting architecture is stable too.
Example: A food-delivery application has capabilities like Order Management, Kitchen Management, Delivery, and Accounting — each becomes a service.

Key Point: Services should be organized around business concerns, not technical concerns. Do not create a "database service" or a "logging service." Instead, create services like "Order Service" or "Restaurant Service."

Defining services by applying the Decompose by sub-domain pattern

Domain-Driven Design (DDD) offers an alternative decomposition strategy. DDD says that every complex business has multiple subdomains, and each subdomain has its own domain model.

Pattern: Decompose by Subdomain (DDD)

Problem: How do you decide which services to create?
Solution: Define services that correspond to DDD subdomains.
Benefit: Each service owns its own domain model. This eliminates God Classes and avoids a single, overly complex enterprise model.
Key Concept: Each subdomain has a Bounded Context — the scope within which a domain model is valid. A Bounded Context maps directly to a service boundary.

Both decomposition patterns — by Business Capability and by Subdomain — produce services organized around business concerns. In practice, they often give very similar results.

DDD as the Decomposition Enabler

Without DDD, organizations often try to build a single enterprise-wide data model. This approach fails because:

DDD solves this by using multiple domain models, each within its own Bounded Context. The same real-world entity has different representations in different services:

Service Entity Name Focus
Order Service Order Consumer details, payment, line items
Kitchen Service Ticket Items to prepare, preparation status
Delivery Service Delivery Pickup and drop-off addresses, courier

Consistency across these different representations is maintained through Sagas and domain events. The API Gateway translates between domain models when a user interface needs data from multiple services.

Note: Event Storming is an alternative, collaborative technique for discovering domain models and service boundaries. Teams place domain events on a timeline and group them into aggregates and bounded contexts.

Decomposition guidelines

Two object-oriented design principles guide good decomposition:

Single Responsibility Principle (SRP)

Principle: A class (or service) should have only one reason to change.
Applied to microservices: Each service should be cohesive — it should implement a small, focused set of related functions.

Common Closure Principle (CCP)

Principle: Classes that change together should be packaged together.
Applied to microservices: Code that changes for the same business reason should be in the same service. This prevents a distributed monolith where a single business change requires updating many services at once.

Warning: If every business change requires coordinated deployment of multiple services, you have a distributed monolith — not a microservice architecture. Apply SRP and CCP to avoid this.

Obstacles to decomposing an application into services

Decomposition is not always straightforward. Several obstacles can make it harder or force compromises.

1. Network Latency

When services communicate over the network, each call adds latency. If a particular decomposition leads to too many round-trips between services, the system becomes slow. Solutions include:

2. Synchronous IPC Reduces Availability

If Service A calls Service B synchronously, and Service B is down, then Service A is also effectively down. This reduces overall availability. Solutions include:

3. Data Consistency across Services

In a monolith, you can use a single database transaction to keep data consistent. With microservices, each service has its own database. You cannot use traditional two-phase commit (2PC) across services.

The solution is the Saga pattern: a sequence of local transactions coordinated by messages. Sagas provide eventual consistency instead of immediate consistency. Data that must be atomically consistent must live in the same service.

4. Consistent View of Data

Because each service has its own database, you cannot get a globally consistent snapshot of all data at one point in time. In practice, this is rarely a real problem.

5. God Classes

A God Class is a large class used throughout the application — like an "Order" class that every part of the system depends on. If you keep a single Order class, it couples all services together.

DDD solves this: each service has its own version of the entity, with only the attributes it needs. The Order Service has an Order, the Kitchen Service has a Ticket, and the Delivery Service has a Delivery.

Key Point: God Classes are solved by DDD's concept of multiple domain models. Each Bounded Context defines its own representation of shared business concepts.

Obstacle Problem Solution
Network Latency Too many remote calls Batch APIs or merge services
Synchronous IPC Cascading failures Async messaging or CQRS replicas
Data Consistency No distributed transactions Sagas (eventual consistency)
Consistent View No global snapshot Accept eventual consistency; rarely a problem
God Classes Tight coupling via shared entities DDD: each service has its own entity version

Defining service APIs

After decomposing into services, you need to define each service's API. This is done by assigning system operations to services.

Assigning Operations

Each system operation is assigned to the service that owns the data (information) the operation needs. This service becomes the entry point for that operation.

Cross-Service Collaboration

Some operations need data or actions from multiple services. For example, createOrder starts in the Order Service but must also verify the consumer (Consumer Service), check the restaurant menu (Restaurant Service), create a kitchen ticket (Kitchen Service), and authorize payment (Accounting Service).

This collaboration means that each participating service needs additional API operations that support the cross-service workflow.

Note: The choice of IPC mechanism (REST, gRPC, messaging) is covered in Chapter 3. The coordination pattern for multi-service operations (Sagas) is covered in Chapter 4.

Service API Definition Summary

Step 1: List all system operations (commands and queries).
Step 2: Assign each operation to the service that owns the related data.
Step 3: Identify cross-service collaborations and add supporting operations to collaborating services.
Result: Each service has a well-defined API of commands, queries, and events.

Key Takeaways