Boundaries & contracts
A boundary defines who must coordinate when something changes. Good boundaries enable parallel teams; bad ones look like “one-line change, ten-repo PR” or “only staging catches breakage.”
Boundary types
| Type | Carrier | Contract | Typical failure |
|---|---|---|---|
| In-process module | Package, target, namespace | Public API, no deep imports | Cycles |
| Inter-process | HTTP/gRPC | OpenAPI/Proto + versioning | Implicit fields, breaking changes |
| Async | Messages, events | Schema registry, idempotency keys | Duplicates, reordering |
| Data | DB, cache | Migrations, read/write rules | Shared-table writes |
Design rules
- Slice by capability, not technology: “Billing”, “Notifications” beat mega
utils. - Contract over implementation: Promise schema and semantics; refactor inside freely.
- Anti-corruption layer (ACL): Translate third-party/legacy models at the edge; keep external types out of domain.
- Failure isolation: Timeouts, circuit breakers, bulkheads; document defaults and degradation.
warning
Distributed monolith: many deployables, one database, cross-writes—looks like microservices, behaves coupled. Prefer owned storage per service and collaboration via APIs/events.
API versioning
| Strategy | When | Practice |
|---|---|---|
| Backward-compatible extension | Public REST/JSON | Optional fields only; stable semantics |
| Explicit version | Long-lived clients | /v1 or header; sunset dates |
| Event versioning | Streams | type + schemaVersion; dual-read window |
Breaking changes (major bump or coordinated release):
- Remove/rename fields, change types
- Change error semantics or auth
- Shrink allowed enum values
Context mapping (DDD cheat sheet)
| Relation | Meaning | Integration |
|---|---|---|
| Shared kernel | Shared model subset | Shared lib (keep small) |
| Customer-supplier | Upstream sets API | SLA + contract tests |
| ACL | Incompatible models | Translate at boundary |
| Open host | Generic subdomain | Documented public API |
| Separate ways | No integration | Avoid fake coupling |
Contract tests in delivery
| Role | Responsibility |
|---|---|
| Provider | Publish contract artifacts (Pact, OpenAPI snapshots) |
| Consumer | CI verifies compatibility; failures block merge |
| Events | Consumers test sample payloads |
Wire into CI/CD: contract failure = compile failure.
Review prompts
- Does a new dependency import another module’s “internal” package?
- Are timeout, retry, and idempotency symmetric across the boundary?
- Stable error model (machine code + human message)?
- Any “temporary” shared DTO used by more than two modules?