pa-01 — Microservices Decomposition & Service Contracts
The first question every platform architecture answers is where are the boundaries? The Apple JD asks for "software architecture and systems design, including microservices decomposition and service contracts." Get the boundaries right and services evolve independently; get them wrong and you build a distributed monolith — services that must deploy together, share a database, or call each other synchronously per request. That's a monolith with network latency, partial failure, and serialization overhead added: strictly worse.
This lab makes decomposition measurable. You build a service-dependency analyzer that detects the structural symptoms of bad boundaries — cycles, high coupling, layering violations — and computes blast radius (who breaks if a service breaks). These are the numbers an architect brings to a decomposition review.
1. What is it?
Decomposition is splitting a system into services along bounded contexts (Domain-Driven Design): cohesive business capabilities with a clear responsibility, owned by one team, independently deployable, owning their own data. The boundary test is high cohesion inside, loose coupling across.
A service contract is the explicit, versioned interface a service exposes — its API (REST/gRPC, pa-02), its events (pa-03), and the guarantees around them (idempotency, ordering, compatibility). Contracts are how services stay decoupled over time: a producer can change internals freely as long as the contract holds.
The dependency structure of a platform is a directed graph (A→B means "A depends on B"). Its shape tells you whether your decomposition is healthy:
HEALTHY (acyclic, layered) UNHEALTHY (cyclic, "distributed monolith")
web mobile web ──▶ orders ◀──┐
\ / │ │
orders ▼ │
/ \ inventory ─────┘ (cycle: can't deploy alone)
inventory payments │
\ / ▼
postgres postgres ◀── orders (domain→infra: layering violation)
2. Why does it matter?
-
It's the decision that's most expensive to get wrong. Code inside a service is cheap to refactor; a boundary between services is a network API, a team boundary, and a deploy boundary all at once. Architects are hired to get these right because changing them later is a migration (gw-12).
-
The distributed monolith is the #1 failed-microservices outcome. Teams split by technical layer or by data table, end up with services that can't deploy independently, and inherit all the costs of distribution with none of the benefits. Recognizing and measuring the symptoms (cycles, coupling, shared DBs) is core architect judgment.
-
Blast radius is a design and operational tool. Knowing that "if
authfails, these 14 services break" drives where you put bulkheads, circuit breakers (gw-06, pa-09), and async boundaries (pa-03). It's also how you scope an incident. -
Contracts are how a platform scales to many teams. With good contracts and compatibility rules (pa-02), 50 teams ship independently; without them, every change is a cross-team coordination meeting. The architect's leverage is making independent evolution safe.
3. How does it work?
Boundary heuristics (what to draw a line around)
| Signal a boundary is right | Signal it's wrong |
|---|---|
| one team can own it end to end | two teams constantly change it |
| changes rarely ripple to other services | every change forces N other deploys |
| it owns its data; others access via its API | services share a database |
| cohesive single capability | a "utils"/"common" service everything depends on |
| can be deployed independently | must deploy in lockstep with others |
The structural analyses (what the code computes)
- Cycle detection (
Cycles, Tarjan SCC). A dependency cycle means those services cannot evolve or deploy independently — the definitional distributed monolith. A healthy decomposition is a DAG. - Blast radius (
BlastRadius). Reverse-reachability: every service that transitively depends onX. The set that breaks ifXbreaks. - Coupling (
FanIn/FanOut). High fan-in = a critical dependency (large blast radius; protect it). High fan-out = a fragile service (broken by many others; it's doing too much or is too chatty). - Layering rules (
LayeringViolations). Architectural constraints like "domain logic must not depend on infrastructure" or "no service may depend on a higher layer," enforced over the graph. These become automated fitness functions in CI (pa-10).
Integration styles (how services should — and shouldn't — couple)
GOOD: API call (sync, pa-02) or event (async, pa-03) across a contract.
GOOD: each service owns its data; others go through its API.
BAD: shared database (an integration database) — couples schemas + deploys.
BAD: synchronous chains N deep — latency + cascading failure (pa-09).
Evolving boundaries: the strangler fig
You rarely get boundaries right up front. The strangler-fig pattern migrates incrementally: route a slice of traffic/functionality to the new service, grow it, and retire the old path — never a big-bang rewrite (gw-12). Contracts + the outbox (pa-05) make the data migration safe.
4. Core terminology
| Term | Definition |
|---|---|
| Bounded context | A cohesive business capability with its own model and boundary (DDD). |
| Service contract | The explicit, versioned interface (API + events + guarantees) a service exposes. |
| Distributed monolith | Services that must deploy together / share data / call synchronously per request. |
| Cohesion / coupling | How related a service's responsibilities are / how dependent services are on each other. |
| Fan-in / fan-out | Number of services depending on X / number X depends on. |
| Blast radius | The set of services affected if a given service fails. |
| Cycle (SCC) | A group of services mutually (transitively) dependent — cannot deploy independently. |
| Integration database | An anti-pattern: multiple services sharing one database. |
| Layering rule | An architectural constraint on which layers may depend on which. |
| Strangler fig | Incrementally replacing a system by routing slices to the new one. |
| Conway's Law | System structure mirrors org communication structure; boundaries are socio-technical. |
5. Mental models
-
Services are organs, not Lego bricks. A good boundary has high cohesion (an organ does one job) and a thin, well-defined interface (contracts = the bloodstream). Splitting by technical layer is like separating "all the left halves" from "all the right halves" — maximal coupling across the cut.
-
The dependency graph is an X-ray. You can't see coupling in a wiki diagram, but cycles, fan-in hotspots, and layering violations are visible in the graph. The analyzer is the X-ray machine; the architect reads the film.
-
Conway's Law is gravity. Your architecture will mirror your org chart. If two teams own one service, it'll fracture along the team line; if one service needs two teams to change it, that's a boundary in the wrong place. Design the boundaries and the team topology together.
-
A contract is a promise you can keep while changing your mind. It lets you rewrite a service's internals freely. The moment consumers depend on something not in the contract (a DB table, a response field's incidental order), you've lost the freedom and gained a distributed monolith.
6. Common misconceptions
-
"Microservices = good, monolith = bad." A well-structured monolith beats a distributed monolith every time. Microservices buy independent deployability and scaling at the cost of distribution complexity; pay it only when you need what it buys (Fowler's "microservice premium"). Many systems should start as a modular monolith.
-
"Split by technical layer (UI / logic / data services)." That maximizes cross-cutting coupling: every feature touches every layer, so every change is a multi-service deploy. Split by capability (orders, inventory, payments), each owning its full stack.
-
"Shared database is fine, it's just one team's data." An integration database couples schemas and deploys across services and destroys independent evolution. Each service owns its data; others use its API or its events (pa-05 outbox).
-
"Smaller services are always better." Nano-services explode the number of network hops, contracts, and failure modes. Size to a bounded context a team can own, not to "one function per service."
-
"We'll fix the boundaries later." Boundaries are the hardest thing to change later (they're migrations). Spend architect time here up front, and design for evolution (strangler fig) when you must move one.
7. Interview talking points
-
"How do you decide service boundaries?" Bounded contexts / capabilities, not technical layers or data tables. Test: high cohesion inside, loose coupling across, independently deployable, one team owns it, owns its data. Name the distributed-monolith anti-pattern and how you'd detect it (cycles, shared DBs, per-request sync chains).
-
"How do you know a decomposition has gone wrong?" Measurable symptoms: dependency cycles, a service with huge fan-in that everything waits on, services that always deploy together, shared databases, synchronous call chains. I'd put cycle/layering checks in CI as fitness functions (pa-10).
-
"What's blast radius and why compute it?" The set of services that break if one fails (transitive dependents). It tells you where to put bulkheads/breakers and async boundaries, and it scopes incidents. Reducing blast radius is a primary goal of decomposition.
-
"How do services stay decoupled as they evolve?" Explicit, versioned contracts (pa-02) with compatibility rules, owning their own data, and async events (pa-03) where temporal coupling is unacceptable. The contract lets a producer change internals without breaking consumers.
-
"Monolith → microservices: how?" Strangler fig: carve one bounded context at a time behind a contract, move its data with an outbox (pa-05), route a slice of traffic, verify, retire the old path. Never a big-bang rewrite (gw-12). Stop when the remaining monolith is fine — you don't have to split everything.
-
"When would you NOT use microservices?" Early-stage / small team / unclear domain boundaries: a modular monolith ships faster and lets you learn the real boundaries before paying the distribution tax.
8. Connections to other labs
- pa-02 (API design) — contracts are how the boundaries you draw here stay decoupled over time.
- pa-03 / pa-04 (events / log) — async communication breaks the temporal coupling that turns sync call-chains into distributed monoliths.
- pa-05 (outbox) — how a service owns its data and publishes events without a shared DB or a dual write.
- pa-10 (architecture in practice) — the cycle/layering checks here
become automated fitness functions in CI; the
servicegraphis reused. - gw-12 (migration) — strangler-fig boundary moves are migrations, run with the rollout ladder.
- db-/gw- — the services you decompose are the storage engines, gateways, and consensus systems built earlier.