# 05. Decomposition

How to slice a system along responsibilities, distinguish real boundaries from accidental folders, and use cohesion, coupling, and bounded contexts in practice.

- **Author:** Fedor Chishchin
- **Level:** advanced
- **Reading time:** ~25 minute read
- **Published:** 2026-05-23
- **Source:** https://fedorthinks.com/learn/software-architect-05-decomposition

---

## 1. The core idea

A good boundary runs where the system naturally changes responsibility, language, or rate of change.

### Decomposition starts with reasons for change

> If one door keeps getting moved because of the warehouse and another because of the guest entrance, those are two zones of the building. They don't belong on the same wall.

A component with **high cohesion** contains things that change for the same reason. A component with **low coupling** knows little about its neighbours and talks through a clear contract. These two properties matter more than the names of any patterns.

Bad decomposition often looks tidy in the IDE: `controllers`, `services`, `repositories`, `utils`. But if a small product tweak forces you to touch five layers and three domains, the system is still badly sliced. Good decomposition lets you change checkout, billing, or identity without mentally loading the rest of the product.

### A boundary is also ownership

> A city map is useless if no one knows who repairs the bridges, who runs the traffic lights, and who to call after a crash.

An architectural boundary needs an owner. If a module is "shared" but everyone modifies it, it quickly becomes a risk zone. If a bounded context belongs to a team, that team gets the right to evolve the internal model and the duty to keep the external contract stable.

So decomposition and team topology are linked. Sometimes the right question isn't "which service do we extract" but "which team should own this flow end to end."

## 2. Example and anti-example

The same domain can be cut by tables, by layers, or by business flows. The third one usually works.

### Example: checkout as a vertical slice

> The checkout counter in a store owns the purchase journey: cart, price, payment, receipt. It isn't a "layer of buttons" or a "layer of SQL."

In e-commerce, checkout can own the pricing snapshot, payment intent, order draft, and failure handling. Catalog still owns products, identity owns users, warehouse owns stock. Checkout doesn't copy other teams' logic — it asks for data through contracts and publishes an `OrderPlaced` event.

That vertical slice is comfortable: the team sees the user flow whole, and changes in payment don't spread across every subsystem.

### Anti-example: technical layers as the only boundary

> If all the chefs chop vegetables in one room, fry in a second, and salt in a third, a simple order starts to require coordinating the whole building.

The system is split into `user-service`, `order-service`, `payment-service`, but inside each service sits a shared `common-domain`, a shared database, and shared validators. Formally there are services. Functionally there are no boundaries.

It gets worse when teams own layers: a frontend team, a backend team, a database team. Every feature turns into a coordination project. That inflates lead time and cognitive load.

#### Self-check

- What is the reason for change in this module?
- Can the team change it without negotiating with five neighbours?
- Does the boundary have an owner?
- Which data and invariants actually belong to this context?

Next module: [Layers and dependencies](./software-architect-06-layers).
