The hardest decision when designing a microservices architecture is not the technology, but where to draw the boundaries: what goes into each service and what stays out. A poor decomposition leads to coupled services that must be deployed together, shared data schemas, and changes that propagate in cascade. A good decomposition produces cohesive, autonomous services that are easy to evolve.

In this lesson we will study the two main decomposition strategies (by business capability and by subdomain), introduce the concept of bounded context from Domain-Driven Design (DDD), and learn how to calibrate the appropriate size and granularity of a service.

Contents

  1. The problem of boundaries
  2. Strategy 1: decomposition by business capability
  3. Strategy 2: decomposition by subdomain (DDD)
  4. Bounded Contexts and ubiquitous language
  5. Granularity and appropriate size
  6. Coupling, cohesion, and the context map
  7. Common mistakes and tips
  8. Exercises
  9. Conclusion

  1. The problem of boundaries

A poorly placed boundary has an enormous cost: correcting it involves moving data, rewriting APIs, and coordinating several teams. The goal of a good boundary is to maximize cohesion within the service (the things that change together stay together) and minimize coupling between services (changing one does not force you to change others).

Property What we seek
Cohesion High: what changes together lives together.
Coupling Low: services depend little on each other.
Autonomy High: each service deploys and operates on its own.

The golden rule: decompose by business domain, not by technical layer. Services such as "database service", "validation service", or "utilities service" are an antipattern, because no business change is resolved by touching a single service.

  1. Strategy 1: decomposition by business capability

A business capability is something the organization does to generate value, regardless of how it implements it. They are identified by analyzing what the company does, not what tables it has.

At an insurer, typical capabilities would be:

  • Customer management
  • Policy underwriting
  • Claims handling
  • Billing and collections
  • Reinsurance
# Each business capability becomes a candidate service
capabilities:
  policy_underwriting:
    actions: [create_policy, renew_policy, cancel_policy]
    published_events: [PolicyCreated, PolicyRenewed, PolicyCancelled]
  claims_handling:
    actions: [open_claim, appraise, indemnify]
    published_events: [ClaimOpened, ClaimIndemnified]

Each capability groups the actions and events that belong to it. This strategy is stable, because business capabilities change far less often than technology.

  1. Strategy 2: decomposition by subdomain (DDD)

Domain-Driven Design decomposes the overall domain into subdomains, which it classifies into three types:

Subdomain type Definition Recommended strategy
Core What differentiates the company; its competitive advantage. Invest the best effort; careful in-house development.
Supporting Necessary but not differentiating. Simpler development, possible partial outsourcing.
Generic A common problem already solved in the market. Buy or use a standard solution.

For an insurer, the actuarial calculation of premiums is probably a core subdomain; document management is supporting; and sending emails is generic (an external provider is used).

This classification guides investments: it makes no sense to build a generic subdomain from scratch when mature solutions exist.

  1. Bounded Contexts and ubiquitous language

A bounded context is an explicit boundary within which a domain model and its vocabulary have a precise and unique meaning. The central idea is that the same word can mean different things in different contexts.

Consider the term "Customer":

  • In Underwriting, a customer is a policyholder with the capacity to contract and a risk profile.
  • In Billing, a customer is a debtor with an account and some receipts.
  • In Claims, a customer is an injured party or beneficiary.

Forcing a single "Customer" model for all three contexts produces a gigantic class with dozens of fields that no one fully understands. The DDD solution is that each bounded context has its own Customer model, tailored to its needs.

// Same concept, two models depending on the bounded context.

// Underwriting context: we care about risk
package com.fiatc.underwriting;
public class Customer {
    private String id;
    private RiskProfile riskProfile;
    private List<Policy> activePolicies;
}

// Billing context: we care about collections
package com.fiatc.billing;
public class Customer {
    private String id;            // same identifier to correlate
    private PaymentMethod paymentMethod;
    private List<Receipt> pendingReceipts;
}

The id acts as the link between contexts, but each model only contains what its domain needs. The ubiquitous language is the common vocabulary shared by developers and business experts within a context, and it is reflected directly in the code.

graph LR
    subgraph "Underwriting Context"
        C1[Customer: policyholder]
    end
    subgraph "Billing Context"
        C2[Customer: debtor]
    end
    subgraph "Claims Context"
        C3[Customer: beneficiary]
    end
    C1 -. same id .- C2
    C2 -. same id .- C3

Each bounded context is a natural candidate for a microservice. This is the most reliable rule for drawing boundaries.

  1. Granularity and appropriate size

How big should a service be? There is no magic lines-of-code metric. Better indicators:

  • Functional cohesion: a service should be describable with a single business sentence.
  • Transactionality: what needs to be in the same ACID transaction should live in the same service.
  • Rate of change: what changes together should be together.
  • Team autonomy: a service fits in the head of a small team.
Symptom Diagnosis Action
Many calls between two services for each operation Too fragmented Merge
A business change requires touching 5 services Poorly placed boundaries Redesign boundaries
A service with dozens of disparate responsibilities Too large Split
You constantly need distributed transactions Incorrect split Regroup by consistency

Practical advice: start with services that are larger than you think you need and split them when real pain appears. It is much easier to split a service than to merge two.

  1. Coupling, cohesion, and the context map

DDD proposes documenting how contexts relate through a context map. Some relationship patterns:

  • Customer/Supplier: one context consumes another's API; the supplier must respect the customer's needs.
  • Conformist: the consumer simply adapts to the supplier's model.
  • Anticorruption Layer (ACL): the consumer translates the foreign model into its own to avoid contamination.
// Anticorruption layer: translates the external model into the internal model
public class CustomerAcl {
    public Customer translate(ExternalCustomerDTO dto) {
        // We don't let the external model "leak" into our domain
        Customer customer = new Customer();
        customer.setId(dto.getCode());            // name mapping
        customer.setRiskProfile(calculate(dto));  // semantics adaptation
        return customer;
    }
}

The anticorruption layer is invaluable when integrating legacy systems: it isolates your clean model from the quirks of the external system.

Common Mistakes and Tips

  • Decomposing by technical layers (data, validation, logic): any business change touches all services. Decompose by domain.
  • A single canonical model for the entire company: produces gigantic, unmanageable models. Adopt bounded contexts.
  • Services that are too small from the start: start large and split when real pain appears.
  • Ignoring business experts: the correct boundaries emerge from conversations with those who know the domain (techniques such as Event Storming help).
  • Sharing tables between contexts: breaks the boundary. Use APIs or events.

Exercises

  1. Explain in your own words what a bounded context is and why the same word ("Account", "Product"...) may need different models in different contexts.
  2. Classify the following subdomains of a bank as core, supporting, or generic, and justify: (a) credit scoring engine, (b) document template management, (c) SMS sending.
  3. You come across two services, "Orders" and "Order Lines", that call each other dozens of times per operation and share transactions. What diagnosis do you make and what action do you take?

Solutions

  1. A bounded context is a boundary within which a model and its vocabulary have a unique and coherent meaning. The same word needs different models because each context cares about different attributes and behaviors: "Product" in the catalog carries a description and images, while in the warehouse it carries location and stock. Unifying them creates an overloaded model.

  2. (a) Core: scoring differentiates the bank and is its competitive advantage; careful in-house development. (b) Supporting: necessary but not differentiating; simple development. (c) Generic: a solved problem; an SMS provider is contracted.

  3. Diagnosis: they are too fragmented; the fragmentation crosses a transactional boundary. Action: merge them into a single "Orders" service that contains the lines as part of its aggregate, eliminating the network calls and enabling local ACID transactions.

Conclusion

We have seen that the key to a good microservices architecture lies in drawing boundaries by business domain, relying on business capabilities and, above all, on the bounded contexts of DDD. The correct granularity seeks high cohesion and low coupling, and it is advisable to start with large services and split them when real pain appears.

Once the services and their boundaries are defined, the practical question arises of how they talk to each other without becoming overly coupled. The next lesson, API Gateway, Service Discovery, and Inter-Service Communication, addresses precisely the mechanisms of synchronous and asynchronous communication, and the infrastructure that makes them possible.

Application Architecture Course

Module 1: Fundamentals of Application Architecture

Module 2: Design Principles and Tactics

Module 3: Architectural Styles and Patterns

Module 4: Distributed Architectures and Microservices

Module 5: Event-Driven Architectures and Messaging

Module 6: Domain-Driven Design (DDD)

Module 7: Data and Persistence

Module 8: Cloud Architecture and Deployment

Module 9: Quality, Security and Observability

Module 10: Evolution, Governance and Case Studies

© Copyright 2026. All rights reserved