--- name: architecting-service-boundaries description: Decides how to split (or not split) a backend into services — modular monolith by default, boundaries along business capabilities, data ownership per service, sagas over distributed transactions, strangler-pattern extraction. Use when the user asks whether to adopt microservices, how to split a monolith, where service boundaries belong, how services should share data, or how to handle transactions across services. Do not use for message-broker mechanics (handling-async-messaging) or REST endpoint design (designing-rest-apis). --- # Architecting Service Boundaries ## When to use / when NOT to use - **Use for:** monolith-vs-services decisions, drawing/reviewing service boundaries, cross-service data and transaction design, extraction plans. - **Do NOT use for:** broker/queue mechanics (handling-async-messaging), endpoint design (designing-rest-apis), or infra scaling (scaling-backend-services). ## Core rules 1. **Default to a modular monolith.** One deployable with enforced internal module boundaries. Split a module out only on a proven trigger: independent scaling need, independent deploy cadence, or team-ownership friction — never "because microservices." 2. **Boundaries follow business capabilities, not technical layers.** - ✅ `billing`, `catalog`, `fulfillment` - ❌ `api-service`, `business-logic-service`, `database-service` 3. **Each service owns its data.** No other service reads its tables; integration happens through its API or published events. A shared database is one service wearing several trench coats. 4. **Keep synchronous call chains shallow — ~2 hops max.** Request → A → B is acceptable; A → B → C → D couples four uptimes and multiplies latency. Deeper flows go asynchronous via events. 5. **Distributed transactions are sagas.** A sequence of local transactions with explicit compensating actions for each step. Never reach for two-phase commit; design the compensation before the happy path. 6. **Contracts are versioned and backward compatible.** Additive changes only within a version; breaking changes get a new version with a deprecation window. A consumer must never be forced to deploy in lockstep with a provider. 7. **Extract with the strangler pattern.** Route a slice of traffic through the new service while the monolith still serves the rest; retire monolith code only after parity is proven. Big-bang rewrites forfeit the rollback path. 8. **Watch for the distributed monolith.** Services that must deploy together, share a schema, or break when one is down have all of the costs of microservices and none of the benefits — re-merge or re-draw the boundary. ## Workflow 1. List business capabilities and the teams that own them; sketch candidate boundaries there. 2. For each candidate split, name the concrete trigger from rule 1. No trigger → stays a module. 3. For each boundary: define the owned data, the exposed contract, and the events published. 4. Map every cross-boundary write flow as a saga with compensations (rule 5). 5. Plan extraction via strangler routing with a rollback switch (rule 7). 6. Validate the design: for each service, confirm it can deploy alone with every other service frozen, and survive (degraded, not down) any single dependency being offline. Any "no" is a rule-8 smell — redraw before building. ## Edge cases & failure modes - **Two services keep changing in the same PRs** → boundary is wrong; merge them or move the shared concept to one owner. - **A service needs another's data constantly** → replicate via events into a local read model instead of chatty sync calls. - **Reporting needs to join across services** → dedicated analytics store fed by events; never grant cross-service table access. - **Saga compensation impossible** (e.g., email already sent) → make the step last, or design it to be semantically reversible ("correction" message). ## References Decision tables, saga and strangler sketches: see [references/patterns.md](references/patterns.md).