The Architecture Reference

Ms decomposition · Microservices · Intermediate

Modeling Service Boundaries

Where to draw the lines — using information hiding, coupling/cohesion, the four (or five) types of coupling, and just-enough domain-driven design (aggregates and bounded contexts) to find stable boundaries.

Ms decomposition Intermediate ⏱ 5 min read Complete

🧭 Analogy

A well-run hospital groups people by what they accomplish together — a maternity ward, an emergency department — each with its own staff, supplies, and records, exposing only a referral interface to the rest. A badly-run one groups by job title (all the nurses here, all the records there), so every patient journey crosses every department. Service boundaries work the same way: organize around business capability, not technical layer.

The three foundations

  • Information hiding (Parnas) — minimize the assumptions modules make about each other: “the connections between modules are the assumptions which the modules make about each other.” Hide what changes; expose a stable contract. “If you hide it now, you can always decide to share it later.”
  • Cohesion — “the code that changes together, stays together.” Strong cohesion means a change happens in one place and releases quickly.
  • Coupling — loosely coupled services know as little as needed about collaborators. Constantine’s law: “a structure is stable if cohesion is strong and coupling is low.”

Types of coupling — from desirable to pathological

graph TD
D["Domain coupling<br/>uses another's functionality"] -->|"acceptable, minimize"| OK["Looser"]
T["Temporal coupling<br/>both must be up at once"] -->|"mitigate with async"| OK
P["Pass-through coupling<br/>forwarding data downstream"] --> MID["Reduce"]
CC["Common coupling<br/>shared mutable data"] --> MID
X["Content coupling<br/>reaching into internals"] -->|"avoid entirely"| BAD["Pathological"]
  • Domain couplingOrder Processor calls Warehouse and Payment. Largely unavoidable and loose; send only what’s needed (a Pick Instruction with packing info, not the whole order with credit-card details).
  • Temporal coupling — a synchronous call needs the callee up now; mitigate with asynchronous communication.
  • Pass-through coupling — a service forwards data only because something downstream needs it; fix by constructing it locally or treating it as an opaque blob.
  • Common coupling — multiple services share mutable data (both writing Order.Status). Benign for static read-only reference data; for changing data, fix with a finite state machine owned by a single source-of-truth service that rejects invalid transitions.
  • Content coupling (pathological) — a service reaches into another’s database and changes its state directly. Avoid it; it destroys information hiding.

Just enough domain-driven design

  • Ubiquitous language — use the domain’s real terms in code (a bank collapsing everything into a generic “arrangement” is the counter-example).
  • Aggregate — a self-contained domain concept (Order, Invoice, Stock Item) with state, identity, and a life cycle, often a state machine. One aggregate is managed by exactly one microservice. It can say no to illegal transitions. Cross-aggregate references should be explicit (e.g., a URI).
  • Bounded context — a larger boundary that hides internal detail and contains one or more aggregates (warehouse forklift types are nobody else’s business).

Both aggregates and bounded contexts are units of cohesion with well-defined interfaces, so both make good boundaries. Rule of thumb: start coarse — map whole bounded contexts to services — then split along aggregate boundaries later, hiding the split behind a coarser API if you like.

graph TD
subgraph BC["Warehouse bounded context (start as 1 service)"]
  A1["Stock Item aggregate"]
  A2["Shelf aggregate"]
  A3["Pick List aggregate"]
end
BC -->|"split later if needed"| S1["Stock service"]
BC --> S2["Picking service"]

💡 Event Storming

Alberto Brandolini’s Event Storming brings technical and nontechnical stakeholders together with coloured sticky notes to discover, bottom-up, domain events → commands → aggregates → bounded contexts. The real output is shared understanding; the hard part is getting the right people in the room.

Don’t be dogmatic

Newman’s defaults are organizational and domain-driven boundaries, mixed as the problem demands. He acknowledges alternatives: volatility-based (extract frequently-changing parts), data (privacy/regulatory — a PCI “red zone”), technology (different runtime needs), and organizational (Conway’s law — avoid boundaries spanning teams). “If someone says ‘the only way to do this is X!’ they are likely just selling you more dogma.”

⚠️ Beware the chatty pair

On the whiteboard, watch for circular references and two overly chatty services constantly calling each other — a strong sign they should be a single service. Premature, fine-grained splitting along the wrong seam is far more expensive to undo than starting coarse.

🔑 Key insight

Signs your boundaries are wrong: services you always change together, excessive cross-team communication, lots of inbound feature requests to one service, and trouble agreeing on a model. If architecture and organization conflict, the organization wins — so design them together.

See also

When to use it — and when not

✅ Reach for it when

  • You are deciding how to split a system into services or modules.
  • You want to evaluate whether two services are too chatty or wrongly coupled.
  • You need a shared vocabulary (aggregates, bounded contexts) for boundaries and APIs.
  • You want a defensible, business-aligned seam rather than a technical-layer split.
  • You are running Event Storming or a domain-modeling workshop with stakeholders.

⛔ Think twice when

  • The domain is still unstable or poorly understood — defer splitting until it settles.
  • You only need to move one existing piece — see the strangler fig pattern instead.
  • A single team owns everything and a modular monolith already gives you what you need.
  • You are tempted to split fine-grained from day one — start coarse instead.

Check your understanding

Score: 0 / 4

1. What does Constantine's law state?

Strong cohesion (code that changes together stays together) plus loose coupling (services know as little as needed about each other) gives a stable structure.

2. Which type of coupling is pathological and must be avoided?

Content coupling destroys information hiding and blurs ownership; domain coupling is largely unavoidable and loose.

3. In DDD, what is an aggregate?

An aggregate (e.g., Order) is often a state machine; one aggregate is managed by exactly one microservice, and cross-aggregate references should be explicit.

4. What is Newman's recommended starting granularity?

Start coarse with bounded contexts; you can hide a later split behind a coarser API. 'Turtles all the way down.'

Comments

Sign in with GitHub to join the discussion.