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 auth fails, 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 rightSignal it's wrong
one team can own it end to endtwo teams constantly change it
changes rarely ripple to other servicesevery change forces N other deploys
it owns its data; others access via its APIservices share a database
cohesive single capabilitya "utils"/"common" service everything depends on
can be deployed independentlymust 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 on X. The set that breaks if X breaks.
  • 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

TermDefinition
Bounded contextA cohesive business capability with its own model and boundary (DDD).
Service contractThe explicit, versioned interface (API + events + guarantees) a service exposes.
Distributed monolithServices that must deploy together / share data / call synchronously per request.
Cohesion / couplingHow related a service's responsibilities are / how dependent services are on each other.
Fan-in / fan-outNumber of services depending on X / number X depends on.
Blast radiusThe set of services affected if a given service fails.
Cycle (SCC)A group of services mutually (transitively) dependent — cannot deploy independently.
Integration databaseAn anti-pattern: multiple services sharing one database.
Layering ruleAn architectural constraint on which layers may depend on which.
Strangler figIncrementally replacing a system by routing slices to the new one.
Conway's LawSystem 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 servicegraph is 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.