How do the component cohesion principles (REP, CCP, CRP) apply when the "components" are independently deployed services or packages in a large organisation, and what evidence would you use to justify merging or splitting one?
answer
- Service = independently releasable component
- Distributed monolith = CCP violation across services
- Shared lib / shared schema = CRP violation
- Published contracts need REP: version, compat, deprecation window
- Justify with co-change, coordinated deploys, irrelevant-release rate
basics
~20 sA service is a releasable component, so the same rules hold: keep code that changes together in one service (CCP), avoid shared libraries that force everyone to redeploy (CRP), and version every published contract (REP). Justify changes with co-change and release data.
solid answer
~60 sAn independently deployed service or published package *is* a component, so the cohesion principles transfer directly — only the costs get bigger. **CCP** argues against boundaries that every feature change must cross. If a change consistently requires coordinated edits and lock-step deploys of three services, that is a distributed CCP violation and the strongest single argument for merging them. **CRP** argues against the shared `common` library or shared database schema that couples every service's release to every other's. Duplicating a small amount of code is often cheaper than the coupling the shared artifact creates. **REP** applies to every published contract — API schema, event payload, client SDK — which needs versions, compatibility rules, deprecation windows and a support period, because consumers deploy on their own schedule. **Evidence to justify a change**: components touched per change request (from VCS/ticket history), coordinated-deploy frequency, per-consumer irrelevant-release rate, cross-service call fan-out, incident correlation, and team ownership overlap. Combine those with the direction of travel — young systems favour CCP, mature platforms favour REP/CRP.
go deeper
Say a service is just a bigger component, so the same rules apply: keep things that change together in one service and version anything other services depend on.
Name the concrete anti-patterns — distributed monolith (CCP), shared common library and shared schema (CRP) — and note that published APIs need versioning (REP).
Add the measurable evidence (components per change, coordinated deploys, irrelevant-release rate, tracing fan-out) and the expand/contract mechanics for evolving contracts compatibly.
Make it governance: a shared-code policy, contract compatibility enforcement via schema registry, deprecation and support windows, a cadence for re-partitioning from measured data, alignment with team ownership (Conway), and explicit acceptance of duplication as bought independence.
## Why the principles transfer The defining property of a "component" in Martin's sense is **independent releasability**, not file format. A service that has its own pipeline, its own version and its own deploy schedule is a component in exactly that sense — and so is a published package or an SDK. Therefore REP/CCP/CRP apply unchanged. What changes is the **magnitude of the costs**, because the boundary is now a network and organisational boundary too: - a cross-component change now needs a **coordinated deploy** and usually a **backward/forward-compatibility dance** (expand/contract), not just a version bump; - the failure of a boundary is now a **runtime failure** (partial deploy, schema skew), not just a build break; - boundaries usually coincide with **team boundaries**, so a bad seam becomes a permanent coordination tax. --- ## CCP at service scale > Services should be drawn so that a typical business change lands inside one service. **Distributed CCP violation** — the classic "distributed monolith": adding one field requires a schema change in service A, a DTO change in B, a mapper in C, and a coordinated release of all three, in order. Symptoms: - change requests routinely spawn PRs in ≥3 repositories; - deploys must be ordered and released together; - a rollback of one requires rollback of the others; - shared release trains reappear. This is the same CCP argument as for libraries, but the remedy is heavier: merge services, move the volatile logic to one side of the boundary, or redraw along the actual change axis (usually a business capability / bounded context, which is why DDD's bounded contexts and CCP agree so often). Note the flip side: **CCP does not say "one big service".** It says one *reason to change* per service. A service that must change whenever any of four unrelated stakeholders asks for something also violates CCP. --- ## CRP at service scale Two dominant violations: 1. **The shared library.** A `common`/`platform-core` package that every service depends on. Every change re-releases it; every service must upgrade and redeploy for changes it doesn't use; and it becomes a channel through which one team's change breaks another's runtime. CRP prescribes splitting by usage cluster, keeping shared code to genuinely universal, extremely stable concerns, and preferring **duplication over coupling** for small helpers. (This is the well-known "a little copying is better than a little dependency" trade-off, argued here on CRP grounds.) 2. **The shared database schema.** Multiple services reading one physical schema means every table change is a potential break for consumers who never use those columns — CRP violation at the data layer, and usually a CCP violation as well because the schema becomes a change-magnet. Also relevant: **generated client SDKs**. A single fat SDK for a whole platform forces every consumer to take the whole surface; per-capability clients honour CRP. --- ## REP at service scale Every **published contract** is a released artifact needing REP discipline: REST/GraphQL schemas, event/message payloads, client libraries, and shared data contracts. Requirements: - explicit **contract versioning** (URI version, media-type version, event schema version — the encoding matters less than having one); - **compatibility rules** (typically additive-only within a version; new required fields and removals are breaking); - **deprecation windows** long enough for the slowest consumer to migrate, with consumer telemetry to know when it's safe to remove; - a **support policy** for old versions, since consumers deploy on their own schedule and you cannot atomically upgrade everyone; - a **schema registry** or equivalent so compatibility is checked mechanically rather than socially. Without REP here, you get silent runtime breakage rather than a compile error — the failure mode is strictly worse than in the library case. --- ## Evidence to justify merging or splitting Make it a data argument, not a taste argument: | Signal | Source | Reading | |---|---|---| | Components touched per change request | VCS + ticket linkage | high → CCP violation → consider merging/redrawing | | Coordinated/ordered deploy frequency | CD pipeline history | high → boundary is not a real seam | | Per-consumer irrelevant-release rate | changelogs vs. consumer usage | high → CRP violation → split | | Cross-boundary call fan-out / chattiness | tracing | high → the seam cuts a cohesive interaction | | Co-change (logical coupling) between repos | commit history | strong cross-repo pairs → wrong seam | | Incident correlation | postmortems | services that always fail together are one failure domain | | Ownership overlap | contribution data | two teams editing one service, or one team owning six, both signal misalignment | | Contract-breakage rate | schema registry / API gateway | frequent breaking changes → REP discipline missing | **Direction of travel** matters as much as the snapshot: a young system should be coarse (CCP-dominant) and split later once the change and consumption profiles are known; splitting first, on speculation, produces a distributed monolith with all the costs and none of the independence. --- ## Governance implications for a principal-level answer - Treat component boundaries as **revisable** with a review cadence tied to the measurements above, rather than as fixed architecture. - Publish an explicit **shared-code policy**: what is allowed in shared libraries (stable, universal, no domain logic), what must be duplicated, and who owns each. - Pair the cohesion principles with the **coupling** ones — ADP (no cycles between services, including event cycles), SDP (depend toward the more stable side), SAP (stable things should be abstract, i.e. contracts not implementations). - Watch for **Conway's law**: the partition you can actually sustain is the one that matches the org chart; if you want a different partition, change the ownership too. - Accept **duplication as a deliberate purchase of independence**, and record it so it isn't 'cleaned up' by a well-meaning DRY refactor later.
- A platform team proposes a single shared `common-core` library for all 30 services. What's your response?Push back on CRP grounds: every service would depend on all of it, so any change forces 30 revalidations and redeploys, and it becomes a cross-team break channel. Counter-proposal: split by usage cluster, restrict shared code to genuinely universal and very stable concerns with a strict compatibility policy and named owner, and explicitly allow duplicating small helpers to buy independence.
- When is merging two services the right call?When the evidence shows they are one component: most change requests touch both, deploys must be ordered and released together, rollbacks are coupled, tracing shows chatty synchronous fan-out across the boundary, and they fail together in incidents. That is a distributed CCP violation, and merging removes coordination cost without losing any independence you actually had.
- How does REP change when the contract is an asynchronous event rather than a synchronous API?It gets stricter, because you cannot see or control consumers at call time. You need schema versioning with mechanically enforced compatibility (typically additive-only), a registry to validate producers, consumer telemetry to know who is still on old versions, and long deprecation windows — since a removed field breaks a consumer silently at runtime, possibly on replayed historical messages.
Splitting a restaurant kitchen into separate premises: if every dish needs the sauce building, the grill building and the plating building to coordinate per order (CCP violation), you've created delivery overhead, not independence. And a single shared pantry everyone depends on (CRP violation) means one late delivery stops all three kitchens.
saying these in an interview costs you the question
- Believing cohesion principles apply to libraries but not to services
- Defending a shared `common` library or shared database schema as good reuse across services
- Splitting services up front on speculation instead of from measured change and usage profiles
- Treating duplication as always wrong, ignoring that it can be a deliberate purchase of independence
- Arguing boundaries from taste rather than co-change, deploy-coordination and consumer-usage data
- Ignoring that published API and event schemas are released artifacts needing versioning and deprecation windows