Static coupling metrics like Ca/Ce/I/A/D report a healthy architecture, yet a change to one service still forces coordinated releases of four others. What kinds of coupling do these metrics miss, and how would you govern coupling in a way that actually catches this?
answer
- Static metrics see imports only
- Missing: data, schema, semantic, temporal, deploy, operational, org coupling
- Measure co-change and lock-step deploys, not just graphs
- Fitness functions + contract tests + schema registry + one writer per store
- Goodhart: publish trends, gate on intentional rules
basics
~20 sStatic metrics only see code references. They miss coupling through shared databases, shared message schemas, implicit data meanings, required call ordering, timing assumptions, and shared libraries. Measure it instead by what actually co-changes and co-deploys, and guard boundaries with automated contract tests and fitness functions.
solid answer
~50 sCa/Ce/I/A/D are computed from **compile-time references within one codebase**, so they are blind to the couplings that dominate distributed systems: **data coupling** (two services sharing a database table or an event schema), **semantic/contract coupling** (an implicit meaning — units, null semantics, enum values — that no type expresses), **temporal coupling** (A must be up, or must have committed, when B runs), **deployment/version coupling** (a shared internal library or lock-step release), **operational coupling** (shared cluster, quota, connection pool), and **organizational coupling** (one team's approval gates another's release). Connascence names them precisely: cross-boundary connascence of meaning, algorithm, execution order, timing, value, identity. Better instruments: **logical coupling mined from version control** (which repos/files actually change in the same PR or release train), **deployment lock-step counts**, **incident correlation**, and **fan-in of shared schemas**. Governance: consumer-driven contract tests, schema registries with compatibility rules, explicit versioning and expand/contract migrations, no shared write-model database, and automated architecture fitness functions in CI. Publish trends, not scores, so no metric becomes a target (Goodhart).
go deeper
Say that these metrics only look at code references and miss shared databases and message formats.
Enumerate the invisible categories (data, schema, temporal, deployment) with an example each, and name contract tests as a remedy.
Add measurement: co-change analysis, lock-step deploy rate, schema fan-in; and remedies: one writer per store, schema registry, expand/contract migrations, fitness functions.
Frame the whole thing as governance — automated fitness functions encoding explicit decisions, ADRs recording accepted couplings, team/boundary alignment via inverse Conway, and metric hygiene against Goodhart with trends rather than targets.
### The diagnosis: what static metrics can and cannot see Ca, Ce, I = Ce/(Ca+Ce), A, and D = |A+I−1| are derived from a **static dependency graph of one compilation unit set**. Their inputs are imports, type references, calls, inheritance. Anything that couples two components without a compile-time reference is invisible. The couplings that dominate real systems: 1. **Data coupling / shared persistence.** Two services reading the same tables, or one reading another's tables directly. Zero code references, total coupling: a column rename breaks a service that never imported a line of the other's code. Also: shared caches, shared file layouts, shared object storage conventions. 2. **Contract / schema coupling.** A JSON, Avro, Protobuf, or CSV payload crossing a boundary. Static analysis of either side sees only its own DTO. 3. **Semantic coupling (connascence of meaning).** "amount is in minor units", "empty string means unset", "status 4 means partially refunded", "timestamps are UTC". Nothing in either codebase expresses it; it lives in tribal memory and breaks silently rather than loudly. 4. **Algorithmic coupling (connascence of algorithm).** Both sides computing the same signature, cache key, pagination cursor, ID scheme, or hash. A change must ship to both at once. 5. **Temporal coupling.** Synchronous call chains where B must be up for A to serve; ordering requirements ('the migration job must finish before the reader starts'); sleeps standing in for handshakes. 6. **Deployment / version coupling.** A shared internal library or generated client that every service pins; a monorepo release train; a change that requires N services deployed in a fixed order. This is the symptom in the question. 7. **Operational coupling.** Shared database instance, shared cluster, shared connection pool, shared rate-limit quota, shared secret rotation — one workload's incident becomes everyone's. 8. **Organizational coupling.** A change requires a review or a slot from another team; Conway's law made concrete. **Connascence gives the precise vocabulary:** the metrics measure only weak, static, in-repo connascence (name and type). The failures come from strong or dynamic connascence *crossing deployment boundaries* — exactly the combination of high strength and poor locality. ### Instruments that would have caught it - **Logical coupling / co-change analysis.** Mine the VCS across repos: how often do changes to service A appear in the same PR, ticket, or release window as changes to B, C, D? This is a direct empirical measure of the coupling that matters, and needs no code analysis at all. A co-change matrix over six months is the single highest-signal artifact here. - **Deployment lock-step rate.** Percentage of deployments that require more than one service. A well-decoupled system trends toward 1 service per deploy. Track "independent deployability" as a first-class metric. - **Change failure blast radius / incident correlation.** Which services page together? - **Schema fan-in.** How many consumers read each table/topic/schema, and are any of them reading another service's private store? - **Contract-test coverage of boundaries.** Boundaries without a contract test are boundaries where semantic coupling is unmanaged. - **DORA-style lead time per team** — coupling shows up as coordination latency. ### Governance that actually holds 1. **Make boundaries explicit and enforced.** Architecture fitness functions in CI: ArchUnit/dependency-cruiser/import-linter rules, "no service may hold credentials for another service's database", "no package may import `internal.*` of another module". Automated, in the pipeline, failing the build. 2. **Own your data.** One writer per store; publish changes as events or via an API. This eliminates the largest invisible coupling in one stroke. 3. **Version and evolve contracts deliberately.** Schema registry with backward/forward compatibility checks; additive-only changes; expand/contract (parallel-change) migrations so producer and consumer never need simultaneous deploys; deprecation windows with consumer telemetry. 4. **Consumer-driven contract tests** (Pact-style) so semantic expectations become executable and degree becomes visible — you literally get a list of who depends on what. 5. **Kill lock-step deploys structurally.** Feature flags and backward-compatible protocols so ordering is not required; if a shared library forces synchronized upgrades, either freeze it, vendor it, or fold it into a service. 6. **Design for temporal decoupling** where the domain permits: async messaging, idempotent handlers, retries with backoff, outbox pattern, graceful degradation instead of hard dependency. 7. **Align teams to boundaries** (inverse Conway manoeuvre): if two teams must always ship together, the boundary is in the wrong place — merge the services or move the boundary. ### Metric hygiene: why you publish trends, not scores - **Goodhart's law**: once D or LCOM becomes a target, it stops measuring anything — teams add empty interfaces, split classes arbitrarily, or game the counters. - Metrics are **leading indicators for conversation**, not gates. Gate on *specific, intentional rules* (fitness functions) that encode a decision someone made, and use metrics to *find candidate rules*. - Report **direction over time** and always pair a structural metric with a behavioural one (co-change, deploy coupling, incident correlation). Structure without behaviour is how a clean dependency graph coexists with a distributed monolith. - Record the intent in ADRs so a future team knows which couplings were accepted deliberately (a shared kernel of stable value types is a legitimate choice) versus which are accidental.
- Concretely, how do you break the lock-step deploy between a producer and a consumer of a changed message schema?Expand/contract (parallel change): first add the new field alongside the old and have producers write both; deploy consumers to read the new field with a fallback; verify no consumer reads the old field via telemetry or contract tests; only then remove it. No release is ever ordered, and each side deploys independently.
- What single measurement would you introduce first at an organization complaining about coupling?A cross-repo co-change matrix from version control plus the share of deploys that involve more than one service. Both are cheap, need no instrumentation, and measure the actual pain (coordination) rather than a structural proxy.
- When is accepting strong cross-boundary coupling the right call?When the alternative is worse: a stable shared kernel of value types, a regulated canonical schema, or a legacy system you cannot change. The requirement is that the choice is explicit — recorded in an ADR, with a named owner, a compatibility policy, and a monitored blast radius — rather than accidental.
Two neighbouring houses can share no doors, no walls, and no wiring on the blueprint — a perfect structural separation. But if they share one water main, one septic tank, and one fuse box, the plumber's visit shuts off both. Static coupling metrics read the blueprint; the shared utilities are the data stores, schemas, and release trains nobody drew.
saying these in an interview costs you the question
- Concluding the architecture is healthy because the dependency-graph metrics look good
- Assuming that eliminating code-level dependencies eliminates coupling, while services keep sharing a database
- Turning D, LCOM, or instability into a team KPI, which invites gaming
- Believing microservices are decoupled by definition, ignoring temporal and deployment coupling
- Adding more services or more interfaces as the remedy, which converts cheap local coupling into expensive remote coupling
- Treating contract tests as optional because 'the schema is documented'