Does a monorepo where every service is built and deployed from HEAD violate the Reuse/Release Equivalence Principle (REP)? How should REP be applied when producers and consumers share one repository?
answer
- REP = defined granule, not necessarily semver
- monorepo HEAD: commit is the version, atomic change
- no skew, no diamonds; producer pays migration
- violation = no API surface / no owner / reach-in imports
- external consumers ⇒ real versioned releases return
basics
~20 sNot necessarily. REP demands that reuse happen through a defined, identifiable release. In a monorepo built at HEAD, the commit itself is the release: one version for everything, atomic changes, no pinning. It satisfies REP's intent only if boundaries, ownership and change communication still exist.
solid answer
~50 sREP requires the reused granule to be a released granule — bounded, identified, and changed through a communicated process. A monorepo with enforced single-version consumption substitutes *one global version* (the commit/build) for many artifact versions: every consumer is on the same code, changes are atomic across producer and consumers, and there is no diamond-conflict problem or upgrade backlog. That is a legitimate answer to REP, not a violation — provided the component still has a real boundary (declared public API, visibility rules, ownership) and change is communicated. What genuinely violates REP is unbounded reach: any file importing any other, no owner, no API surface, breakage discovered at runtime. The trade-off: the monorepo shifts cost from consumers (upgrade toil) to producers (you break it, you fix all call sites) and requires heavy tooling — build graph, CI at scale, code ownership. The moment a consumer sits outside the repo, you need real versioned releases again.
code
text · 8 linesMonorepo, REP-compliant boundary (build target as the release granule):
//libs/payments:client visibility = ["//services/checkout", "//services/billing"]
//libs/payments:internal visibility = ["//libs/payments:__subpackages__"]
OWNERS: payments-team CHANGELOG.md: behaviour changes + deprecations
Monorepo, REP violation (no granule at all):
//services/checkout imports libs/payments/internal/RetryLoop directly
# no declared edge, no owner, no API promise -> producer can't change anything safelygo deeper
Say REP is about having a clear, identifiable unit you depend on; in a monorepo the commit plays the role of the version, so it isn't automatically a violation.
Contrast the two models concretely — pinned versions with skew and diamonds versus HEAD with atomic change — and name what still must exist in a monorepo: API surface, ownership, changelog.
Weigh the trade (consumer upgrade toil vs producer migration cost), describe tooling requirements (visibility rules, dependency linting, large-scale refactoring, CI at scale), and cover the hybrid case where external consumers force real versioned releases.
Treat it as an organisational strategy choice tied to team topology, CI budget, external commitments and regulatory needs; define the public-API and deprecation policy, and state the conditions under which you would move between models.
## Restating REP precisely **REP**: *the granule of reuse is the granule of release.* The intent is that reuse happens through a **defined, identifiable, communicated unit** — you can say exactly what you depend on, exactly which state of it, and you learn when that state changes. Version numbers in a package registry are the *usual* implementation of that intent, not the intent itself. ## Two coherent models **Model A — independently versioned artifacts (multirepo, or monorepo publishing packages).** Each component is built and published with its own semver line. Consumers pin versions and upgrade deliberately. - Consumers control timing; can stay on an old version. - Producers can change without immediately fixing every caller (breaking changes are shipped as MAJOR and migrated over time). - Costs: **version skew** (many consumers on many versions of you), **diamond conflicts** (two paths need incompatible versions of a shared dependency), backported fixes, long tail of stale consumers, and slow propagation of security fixes. **Model B — single-version monorepo built at HEAD.** All code lives in one repository; consumers depend on the component at HEAD; a commit is the unit of release. - The **commit SHA is the version** — a real, immutable identifier for the whole world. - Changes are **atomic**: a producer change and all consumer updates land in one commit, so there is no intermediate broken state, no skew, and no diamond problem (there is exactly one version of everything). - Fixes propagate instantly to everyone. - Costs: producers bear migration cost ("you break it, you fix it" across all call sites), CI must build/test the affected slice of a huge graph, large-scale refactoring tooling is mandatory, and *nobody can opt out* of a change — a bad change hits everyone at once. Model B does not abandon REP; it collapses many release granules into one global granule. The Google-style single-version policy is the canonical example. What it *does* abandon is per-component independent versioning — deliberately, in exchange for eliminating skew. ## When a monorepo genuinely violates REP The repository layout is not what matters; the **boundary and the process** are. Red flags: - **No declared public API.** Consumers import internal classes/files directly, so the producer cannot change anything safely and there is no surface to promise stability on. Fix: visibility rules, module/export declarations, enforced dependency rules (architecture tests, build-visibility lists, dependency-cruiser-style checks). - **No ownership.** Nobody is accountable for the shared code, so nobody can approve changes or run a migration. Fix: code-owner files and a component charter. - **No change communication.** Even with atomic commits, consumers need to learn about behaviour changes, deprecations and new capabilities. Fix: changelog per component, deprecation annotations with removal dates, announcements for behavioural changes. - **Ad-hoc reach-in reuse across team boundaries**, e.g. Team A's build compiles Team B's source directory with no declared dependency edge. This is a REP violation in *any* repo layout: reuse with no identified granule. ## Hybrid: monorepo that publishes Many organisations run a monorepo internally *and* publish some components externally. Then you get both models at once: internal consumers on HEAD, external consumers on semver artifacts. Consequences to plan for: - The **public API surface must be explicitly delimited** — what's covered by the compatibility promise versus internal (visible internally, unstable). - **Release cutting** becomes a separate act from committing: tag, build, publish, changelog. - You now support **old versions** for external consumers — backports and support windows — even though internal consumers never see them. - "Works at HEAD" is no longer proof for external consumers; you need compatibility testing against supported released versions. ## Applying REP's *contents* rule inside a monorepo Even at HEAD, the theme/cohesion argument survives in modified form. Without version churn, the pressure changes shape: - **Build and test blast radius** replaces version churn: a themeless mega-component means every change rebuilds and retests everything that touches it, and every consumer's CI is affected. - **Dependency footprint** still matters: depending on a fat target pulls its whole transitive closure into your binary/build. - **Ownership and review load** still argue for coherent, owned components. So you still split by theme and by consumer usage clusters (Common Reuse Principle) and by co-change (Common Closure Principle) — you just do it with build targets and visibility rules rather than published versions. ## Decision guide | Situation | Lean toward | |---|---| | All consumers inside one org, one repo, unified CI, strong refactoring tooling | Single-version HEAD (Model B) | | Consumers outside the org, or independent deployment schedules you don't control | Versioned artifacts (Model A) | | Regulated consumers needing pinned, auditable dependency versions | Versioned artifacts | | Many teams, weak CI capacity, cannot rebuild the world per change | Versioned artifacts, or monorepo publishing packages | | Both internal and external consumers | Hybrid, with an explicit public-API surface | ## Bottom line REP is not "use a package registry". It is: *reuse must go through a bounded, identified, owned, change-communicated unit.* A monorepo can satisfy that with commits, build targets and ownership rules; a multirepo can violate it with copy-paste and floating versions. Judge the process, not the folder structure.
- A single-version monorepo removes diamond dependency conflicts. What does it cost, and who pays?The producer pays. Because everyone is on HEAD, a breaking change must be accompanied by fixes to every call site in the same atomic commit — so large refactors require code-modification tooling and reviewer coordination across teams. It also demands CI that can build and test the affected slice of a very large graph, and it removes the consumer's ability to opt out or delay: a bad change reaches everyone at once, so rollback speed and pre-submit testing become critical.
- Your monorepo component also has external consumers on published versions. What must you add?An explicit public-API surface delimiting what carries the compatibility promise versus what is internal-only; a release process separate from committing (tag, build, publish, changelog); semantic versioning discipline on that surface; support windows and backport policy for released versions still in use; and compatibility testing against supported released versions, since 'green at HEAD' no longer proves anything for someone on 3.2.x.
- How do you keep a shared monorepo library from being reached into, given there are no package boundaries to enforce?Enforce boundaries in tooling rather than trusting convention: build-system visibility rules or export declarations that whitelist who may depend on a target, architecture tests or dependency-graph linters in CI that fail on disallowed edges, code-owner rules requiring the owning team's review, and language-level visibility (internal/private modules) where available. Combine with a written charter stating what the public surface is.
Independently versioned artifacts are like a city where every building renovates on its own schedule — nobody is disrupted, but the street ends up in twelve different decades. A single-version monorepo is a planned campus that renovates everything in one coordinated shutdown: no mismatch anywhere, but the whole campus feels every change, and the renovator must fix every room they touch.
saying these in an interview costs you the question
- "Monorepos violate REP by definition" — REP is about a defined release granule, and a commit under a single-version policy is one.
- "A monorepo means no versioning is needed at all" — the commit is still the version, and any external consumer still needs published, immutable releases.
- "Same repo means no boundaries needed" — without a declared API surface, ownership and visibility rules, the producer can never change anything safely.
- "Multirepo automatically satisfies REP" — copy-pasted code, source-path dependencies or floating `latest` versions violate it regardless of repo count.
- "Atomic commits remove the need for changelogs" — consumers still need to learn about behaviour changes and deprecations even when they can't be on an old version.
- "Single-version policy is free" — it shifts migration cost onto producers and demands serious CI and refactoring-tooling investment.