Skip to content
ArchitectureSolution DesignDecompositionDomain Driven DesignBoundaries

Solution Decomposition

Level:Intermediate (SA, EA)
Duration:1.5-day workshop
Deliverable:Decomposition canvas + domain/service/component hierarchy guide

Quick Navigation


Before we start — the one thing to hold onto

Every system that grows large enough becomes unmaintainable — unless someone deliberately breaks it into parts. I've seen teams start with a clean monolith and end up with something where every change requires understanding the whole thing. The cost compounds quietly, then all at once.

Decomposition is the act of deciding what belongs together, what belongs apart, and where the seams between parts should run — before the code tells you by breaking.

The parts you choose shape everything that follows: which teams can work independently, which changes are safe, and which changes ripple across the whole system.

Keep it in mind.


1. Why decomposition matters — The cost of one big thing

A system built as one undivided whole feels faster to start. It becomes impossibly slow to change.

Here's what happens without decomposition. Every part of the system can reach into every other part. A change in one area breaks something unrelated. Two teams block each other because they touch the same code. Deploying one feature requires deploying everything.

The cost compounds:

  • Change velocity drops — every change requires understanding the whole
  • Team autonomy vanishes — teams cannot work independently
  • Risk increases — a failure anywhere can cascade everywhere
  • Testing becomes exhaustive — you cannot test a part in isolation
  • Deployments become all-or-nothing — one release, one risk
flowchart LR
    S1["🟢 Small System\n1-3 teams, manageable"] -->|"Grows without\ndecomposition"| S2["🟡 Medium System\n4-10 teams, slowing"]
    S2 -->|"Changes conflict\ndependencies multiply"| S3["🔴 Large System\n10+ teams, crippling"]
    S3 -->|"Every change\nis high-risk"| S4["💀 Accidental Monolith\nNothing moves fast"]

Here's the part that catches teams out: they delay decomposition because "we'll do it later when we know the domain better." But the cost of decomposition rises as coupling deepens. The seams you don't choose deliberately get chosen accidentally — by whoever writes the code first.

Undecomposed systems don't stay simple — they become accidental monoliths where every change is expensive.


Try it yourself — The decomposition audit

Think of your system. Answer these questions honestly:

Question Your answer
Can one team deploy without coordinating with another?
Does a change in one area ever break something unrelated?
Can a new engineer understand one part without learning the whole thing?
Do you have a "common" or "shared" service that everything depends on?

If you answered "no" to the first two or "yes" to the last two, your system is telling you it needs decomposition. Write that down before moving on.


2. Strategies for decomposition — By domain, capability, data, team

There are many ways to break a system apart. The strategy you choose determines what kind of boundaries you get.

Four common strategies:

By business domain — split along the natural boundaries of the business. Orders, payments, inventory, shipping. Each domain owns its logic and its data.

By capability — split along what the system can do. Authentication, notification, reporting, search. These are shared capabilities that serve multiple domains.

By data — split along data ownership. Who owns what data, who can read versus write. The boundary follows the data, not the function.

By team — split along organisational boundaries. What can a single team own end-to-end? Conway's Law in action.

No single strategy is always right. Most real systems use a mix — domain boundaries at the top level, capability boundaries within domains.

flowchart TD
    Q{"What's the primary\naxis for splitting?"}

    Q -->|"Business areas\nare distinct"| DOMAIN["By Domain\nOrders, Payments, Inventory"]
    Q -->|"Shared functions\nserve multiple areas"| CAPABILITY["By Capability\nAuth, Notifications, Search"]
    Q -->|"Data ownership\nis the constraint"| DATA["By Data\nWho owns what, who reads/writes"]
    Q -->|"Team autonomy\nis the driver"| TEAM["By Team\nWhat can one team own end-to-end"]

Here's the practical approach I use: start with domain decomposition — it aligns with how the business thinks. Within each domain, identify capabilities — the functions that domain needs. For each capability, identify data ownership — who reads, who writes. Then align team boundaries to the resulting structure.

The best decomposition strategy matches the axis of change — split where things change independently.


Try it yourself — The axis of change test

Think of three recent changes in your system. For each one:

Change What else had to change? Could this have been isolated?
e.g. Changed payment provider Checkout flow, reporting, reconciliation Yes — if Payments was its own boundary

If your answer to the second column keeps pulling in unrelated functionality, your boundaries aren't drawn along the axis of change.


3. Defining boundaries — What belongs inside vs outside

Once you decide to decompose, you must decide exactly where the boundaries run. Getting the boundary wrong is worse than not decomposing at all.

A good boundary groups things that change together and separates things that change independently. Inside the boundary, components are tightly coupled — they share data, call each other directly, and are deployed together. Between boundaries, components communicate through stable interfaces.

The boundary defines what is inside, what is outside, what crosses the boundary — the messages, events, or API calls that flow between components — and what is shared.

flowchart TD
    BOUND["Boundary"] --> INSIDE["Inside\nTightly related\nChanges together"]
    BOUND --> OUTSIDE["Outside\nOther components\nStable contracts"]
    BOUND --> CROSSES["What crosses\nAPIs, events, messages\nVersioned schemas"]
    BOUND --> SHARED["What's shared\nCommon data or services\nMultiple components need"]

Here's the part that catches people out: a boundary isn't a line on a diagram. It's a commitment that what is inside can change without breaking what is outside. If you can't keep that promise, the boundary is wrong.

The boundary contract is simple. Outside sees the interface — API, events, messages. Inside contains the implementation — code, data, logic. What crosses: defined messages with versioned schemas. What does NOT cross: internal data models, implementation details.

A boundary is not a line on a diagram — it is a commitment that what is inside can change without breaking what is outside.


Try it yourself — The boundary checklist

Pick two components in your system. Run them through the checklist:

Question Your answer
Does this functionality change when the other changes? If yes, they belong in the same boundary
Can this be deployed independently? If yes, the boundary is real; if no, it's aspirational
Does this component need the other's internal data? If yes, the boundary is in the wrong place
Is the interface between them stable? If no, the boundary will create friction

Any "no" to the second column means your boundary needs work.


4. Coupling and cohesion — The two forces every decomposition must balance

Every decomposition decision creates two opposing forces. Pull things apart and you reduce coupling but risk losing cohesion. Push things together and you increase cohesion but risk tight coupling.

Coupling is how much one component depends on another. High coupling means a change in one forces a change in the other. Cohesion is how well the things inside a component belong together. High cohesion means the component has a clear, unified purpose.

The goal: high cohesion within components, low coupling between components.

Good decomposition:
  [Orders] ---loose coupling--- [Payments] ---loose coupling--- [Inventory]
  (high cohesion)               (high cohesion)                (high cohesion)

Bad decomposition:
  [Orders + half of Payments] ---tight coupling--- [other half of Payments + Inventory]
  (low cohesion)                                   (low cohesion)

There are types of coupling, from worst to best. Content coupling — one component reaches into another's internals. Reading another service's database table directly. Common coupling — components share global data. Both read/write the same config file or shared table. Control coupling — one component passes control flags to another. "Process this, but skip the validation step." Stamp coupling — components share a data structure but use different parts. Data coupling — components communicate through simple parameters. Clean API call with request/response.

And types of cohesion, from worst to best. Coincidental — elements grouped by accident. Logical — elements grouped by category — "all validators." Functional — every element contributes to a single, well-defined task.

If you can only remember one design heuristic for decomposition, remember this: maximise cohesion, minimise coupling.

High cohesion inside, low coupling outside — the decomposition mantra.


Try it yourself — The coupling audit

List three dependencies between components in your system. Rate each one:

Dependency Type of coupling Could it be improved?
e.g. Orders reads Payments database directly Content coupling — worst kind Yes — use API or events instead

If you have any content or common coupling, your boundaries are leaking. Fix those first.


5. Domain-Driven Design basics — Bounded contexts and ubiquitous language

The business uses words like "customer," "order," and "product." But those words mean different things in different parts of the business. If your system uses the same model for everything, it will fight itself.

Domain-Driven Design provides two foundational tools for decomposition.

Bounded context — a boundary within which a model has a specific, consistent meaning. "Customer" in the sales context is different from "Customer" in the support context.

Ubiquitous language — the shared language between developers and domain experts within a bounded context. Everyone in the context uses the same words to mean the same thing.

flowchart TD
    BIZ["Business Domain\nSales · Support · Fulfilment"]
    BIZ --> BC1["🛒 Sales Context\n'Customer' = prospect/buyer\n'Order' = cart/checkout"]
    BIZ --> BC2["🎧 Support Context\n'Customer' = account holder\n'Order' = complaint/ticket"]
    BIZ --> BC3["📦 Fulfilment Context\n'Customer' = destination\n'Order' = shipment/parcel"]

Here's what catches teams out: they create a single "Customer" object that tries to serve every context. It becomes a God object — bloated, confusing, and impossible to change without breaking something.

A bounded context is a semantic boundary — inside it, words have one meaning; outside, they may have another. Same word, different meaning → different bounded contexts.

Bounded contexts integrate through patterns. Customer-Supplier — upstream context provides, downstream consumes. Conformist — downstream accepts upstream model as-is. Anti-corruption Layer — downstream translates upstream model to its own. Shared Kernel — two contexts share a small, agreed model.

A bounded context is a semantic boundary — inside it, words have one meaning; outside, they may have another.


Try it yourself — The language test

Pick a word your business uses — "customer," "order," "product." Now ask three people in different roles what it means:

Role Their definition Same or different?
Sales
Support
Fulfilment

If the definitions differ, you have multiple bounded contexts — and your system should reflect that.


6. Common decomposition mistakes — And what they cost

Getting decomposition wrong is expensive — and the mistakes are predictable. They appear again and again.

Mistake 1: Decomposing too early. Splitting into microservices before understanding the domain creates boundaries in the wrong places. Moving a boundary is harder than creating one.

Mistake 2: Decomposing too late. Waiting until the monolith is so coupled that decomposition requires a rewrite. The cost rises exponentially with delay.

Mistake 3: Decomposing by technology layer. Splitting into "UI service," "API service," "database service" instead of by business capability. Every feature change touches all layers — coupling is maximised, not minimised.

Mistake 4: Shared databases across boundaries. Two components share one database schema. The boundary is an illusion — any schema change breaks both components.

Mistake 5: Chatty boundaries. Components call each other hundreds of times per request. The network becomes the bottleneck and deployment becomes impossible because every change requires coordinated releases.

Mistake 6: God components. One component that does too much — the "common" or "shared" or "core" service that everything depends on. It becomes the bottleneck for every team.

flowchart TD
    MISTAKES["Decomposition Mistakes"]
    MISTAKES --> M1["Too early\nBoundaries wrong"]
    MISTAKES --> M2["Too late\nRewrite required"]
    MISTAKES --> M3["By layer\nMax coupling"]
    MISTAKES --> M4["Shared DB\nFake boundary"]
    MISTAKES --> M5["Chatty\nNetwork bottleneck"]
    MISTAKES --> M6["God component\nSingle bottleneck"]

    M1 --> FIX["Fix: Wait until coupling hurts,\nthen decompose by domain"]
    M2 --> FIX
    M3 --> FIX
    M4 --> FIX
    M5 --> FIX
    M6 --> FIX

Most decomposition failures aren't wrong technology — they're wrong boundaries. The six deadly sins: too early, too late, by layer, shared DB, chatty, god component.

When you do get it wrong, the Strangler Fig pattern is your friend. Instead of rewriting, incrementally extract capabilities from the monolith. Identify a capability that can be isolated. Build the new component alongside the old. Redirect traffic to the new component. Remove the old implementation. Repeat.

Most decomposition failures are not wrong technology — they are wrong boundaries.


Try it yourself — The mistake inventory

Which of these mistakes exist in your system right now?

Mistake Evidence Cost
e.g. Shared database Orders and Inventory share the same schema Can't deploy Orders without testing Inventory

Pick one. What would it take to fix it? Be specific — not "separate the databases," but "extract Inventory data model, create API, migrate consumers one at a time, drop shared tables."


Putting it all together

Here's the complete picture in one diagram. This is the mental model worth internalising.

flowchart TD
    BIZ["Business Domain\nCapabilities and processes"] --> DDD["Domain Analysis\nBounded contexts\nUbiquitous language"]
    DDD --> DECOMP["Decomposition\nDomain · Capability · Data · Team axes"]
    DECOMP --> BOUND["Boundary Design\nHigh cohesion inside\nLow coupling outside\nStable interfaces"]
    BOUND --> INT["Integration Patterns\nAPIs · Events · Messages"]
    BOUND --> DATA["Data Ownership\nEach context owns its data\nNo shared schemas"]
    BOUND --> TEAM["Team Alignment\nOne team per boundary\nEnd-to-end ownership"]
    INT --> DEPLOY["Independent Deployment\nSeparate release cycles\nSeparate scaling"]
    DATA --> DEPLOY
    TEAM --> DEPLOY

The foundation:

Decompose along the axis of change — what changes together stays together; what changes apart is separated. Maximise cohesion, minimise coupling — high cohesion inside components, low coupling between them. Boundaries are commitments — a boundary says "what is inside can change without breaking what is outside." If you can't keep that promise, the boundary is wrong.


Cheat Sheet — All the key terms

Concept One-Line Memory What It Really Means
Decomposition Choosing seams before code chooses them Breaking a system into manageable, independent parts
Domain decomposition Split by business area Aligns with how the business thinks
Capability decomposition Split by function Aligns with shared services and cross-cutting concerns
Coupling How much components depend on each other Lower is better between components
Cohesion How well things inside belong together Higher is better within components
Bounded context Semantic boundary with consistent meaning Same word can mean different things in different contexts
Ubiquitous language Shared vocabulary within a context Developers and domain experts use the same words
Distributed monolith Fake decomposition — still coupled Multiple services that must deploy together
Shared database Boundary illusion — data not owned Two components sharing one schema
God component Everything depends on one thing The bottleneck for every team

How to know if this landed

You'll know this has landed when someone stops asking "should we do microservices?" and starts asking "where are the natural seams in our domain?" They can identify the axis of change in their system. They draw boundaries along what changes independently, not along technology layers. They catch shared databases and chatty boundaries before they become structural problems. And they can explain bounded contexts using their own business vocabulary — not DDD jargon.


What changes when the mental model clicks

I've run this session with teams ranging from startups with a growing monolith to enterprises with thirty services that deploy together. The gap at the start is usually not about technology — it's about not knowing where to draw the lines.

What changes after this session:

Teams stop decomposing by technology layer and start decomposing by domain. The axis of change test — "what changes together, what changes apart" — is always the moment things click. People stop treating microservices as a goal and start treating them as one possible outcome of good decomposition. Their results get better. They stop blaming the monolith when the real problem was that they drew the boundaries wrong.

The bounded context exercise tends to immediately change how teams talk about their domain. They start noticing that "order" means different things to different people. Their models get cleaner. They stop fighting their own system when the real problem was that they were using one model for multiple contexts.


Book a Workshop

Ready to decompose your systems deliberately?

→ Book a Training Session

or

→ Contact me directly

1.5-day workshop includes domain decomposition exercise with your real business domain, boundary design workshop — draw the seams for your system, coupling and cohesion assessment of your current architecture, bounded context identification and ubiquitous language definition, decomposition canvas creation for your system, and anti-pattern identification — find the mistakes in your current decomposition.

Related Trainings

Next Step

Run this with your team

Every programme is adapted to your context before delivery — your systems, your constraints, your decisions. A short call is enough to work out the right shape and scope.