DEV Community

JorelFuji
JorelFuji

Posted on

REPO LAYOUT VS. RUNTIME ARCHITECTURE

cover image

title: "Monorepo is not Monolith — and the difference decides your CVE response time"
published: false
description: "Repo layout and runtime topology are orthogonal axes. Here's what each one actually costs you in version control, updates, scaling, and scanning."
tags: architecture, devops, security, kubernetes
cover_image:

canonical_url:

Repo layout and deployment topology are orthogonal. Most orgs conflate them, split their runtime to solve a version-control problem, and end up with a distributed monolith that fails together, deploys together, and now also drops packets.

The decision that actually matters is where you enforce a boundary — compiler, or network — and you make that choice once per boundary, not once per company.

The two axes

2x2 matrix: repo layout on the horizontal axis, deployment unit on the vertical axis, with real-world examples in each of the four quadrants

Fig. 1 — The four real options. "Monorepo vs. microservices" is a category error.
Axis Question it answers Reversible?
Source layout (mono/poly-repo) How do teams land code? Yes — painful but mechanical
Deployment unit (monolith/services) What ships and scales independently? No — data migrations aren't git revert

Asymmetry of regret. Repo layout is a two-way door. Runtime topology is a one-way door the moment each service owns its data. Sequence accordingly: change the repo layout first, and only split the runtime once module boundaries have proven stable in a monolith.

Why is "polyrepo + monolith" so common if it's strictly worse?
Because it arrives by accretion, not decision. Someone extracts a shared auth library into its own repo for "reuse," and now a one-line change is two PRs, a release, and a version bump — with none of the deploy independence that would justify it. The tell: your internal libraries have semantic versions but only one consumer.

What actually runs

Side by side: a monolith as one container with four internal modules, versus four separate service containers communicating over the network

Fig. 2 — Same four capabilities. The difference is where the boundary is enforced.

A network call is a function call that acquired five new failure modes and lost its type checker. That's the entire trade:

You gain You pay
Independent deploy Timeouts, retries, backoff, idempotency keys
Independent scale mTLS certs + rotation, SPIFFE identity
Independent failure Distributed tracing, or you debug blind
Per-workload least privilege NetworkPolicy per pair, contract versioning
Team autonomy Eventual consistency where you had a transaction

The availability math nobody runs first

Services in a synchronous request path multiply, they don't average. For nn services each at availability aa :

Apath=an A_{path} = a^{n}

At a respectable a=0.999a = 0.999 per service:

Services in path Path availability Annual downtime
1 (monolith) 99.90% ~8.8 h
5 99.50% ~43 h
20 98.02% ~174 h

The corollary. Microservices only improve availability if calls are asynchronous or optional. A synchronous fan-out of 20 services is strictly less available than the monolith it replaced. If reporting being down can 500 your checkout, you didn't isolate failure — you multiplied it.

Distributed monolith: the smell test
  • Do two services have to be released in a fixed order? → not independent
  • Do they share a database schema? → not independent
  • Does one service's deploy require the other's integration suite? → not independent
  • Does a single user story routinely touch 3+ repos? → boundaries follow the org chart, not the domain
  • Is there a "shared-models" library every service pins? → you have a monolith with extra latency and a build step

Any of these = the costs of both architectures, the benefits of neither. Merge them back.


Version control and updates

An engineering drawing has one revision block. A drawing set has a rev per sheet plus an index saying which revs go together. That index is the whole problem with polyrepo microservices.

Concern Monolith Microservices Monorepo mitigates?
Unit of change Caller + callee in one commit; the compiler catches the mismatch Two commits, two repos; nothing catches it until prod — unless you run consumer-driven contract tests ✅ Atomic commit restores the invariant
Breaking a contract Rename, fix call sites, merge Expand → migrate → contract: 3 releases, maybe 3 teams ✅ Partially — still need runtime bake
"What was running at 03:14?" git tag Deploy manifest / cluster state. Git cannot tell you. ❌ Orthogonal — that's GitOps
Bisecting a regression git bisect, works Bisect across the deploy manifest, by hand ✅ Meaningfully
Update blast radius Full-fleet restart for a one-line fix One deployment — the real win
Base-image bump 1 Dockerfile, 1 PR N Dockerfiles, N repos, N reviews, N releases The killer argument
Dependency confusion risk One lockfile, one registry config N lockfiles, N registry configs, one misconfigured

DORA's actual finding: deployment frequency correlates with loosely coupled architecture and independent testability — not with the number of repos or containers. A well-modularized monolith on trunk-based development beats a badly-split microservice estate on every DORA metric. The architecture is a means; the coupling is the thing.

Scalability

Scaling comparison: six monolith replicas each duplicating idle modules, versus twelve replicas of only the hot resizer service

Fig. 3 — Granularity of scaling is the real dividing line, not code size.

The only scaling argument that survives contact is divergent resource profiles. If your resizer is CPU-bound and spiky while auth is memory-flat and steady, you're sizing every replica for the resizer and paying for idle auth 24/7. That's a real bill.

If every module has the same profile, splitting buys you a network hop and a Grafana dashboard.

Concern Monolith Microservices
Horizontal scale Add replicas behind an LB. Stateless monoliths go much further than folklore suggests. Per-service HPA/KEDA. Efficient only when profiles diverge.
Vertical waste Pod sized for the hungriest module Right-size per service
Failure isolation Reporting's leak OOM-kills auth. Shared fate. Reporting dies alone — iff callers have timeouts and breakers
The database Usually the actual bottleneck Splitting app code does not split the DB. Real split means outbox and sagas where you had BEGIN TRANSACTION.
Team scale Merge contention past ~20–30 engineers — solvable with a merge queue Conway's law made explicit: independent cadence per team

If your monolith is slow because of an unindexed query, microservices will give you the same unindexed query plus a service mesh.

Supply chain and scanning

Pipeline comparison: one repo producing one SBOM and one signed image, versus four services each with their own SBOM and base image, one of which has drifted

Fig. 4 — Microservices shrink each blast radius and multiply the number of blast radii.

This is where the two architectures invert, and where most security programs get it backwards.

Concern Monolith Microservices
SAST One scan, whole call graph. Taint analysis works end to end. Per-service scans. The scanner cannot follow taint across a network hop — cross-service injection is structurally invisible. Under-priced regression.
SCA One lockfile, one fix. But one bad transitive dep taints everything. N lockfiles. Smaller each; drift guaranteed; inventory mandatory.
Findings noise Big image → many findings, most in code paths never executed. Kill this with VEX, not with an architecture change. Slim images, genuinely fewer findings. Real benefit.
Fleet CVE response Hours. Bump, rebuild, roll. Days to weeks — unless monorepo or golden-base auto-rebuild. Ask your worst team, not your best one.
Runtime attack surface One identity, one NetworkPolicy. Compromise = full app + full DB creds. Per-workload SPIFFE identity, per-pair NetworkPolicy, minimal filesystem. The genuine security win.
Policy enforcement One gate Enforce centrally at admission, never per-repo. Per-repo policy rots to the level of the least-staffed team.
Provenance / SLSA One attestation, one control mapping N attestations. SLSA L3 needs a hardened build platform — N times, or one shared platform.

The pattern that actually works, regardless of which side you land on:

golden base image (hardened UBI/distroless)
  └─> bump triggers rebuild of ALL dependents
        └─> SBOM + in-toto provenance attestation
              └─> cosign sign
                    └─> admission control verifies signature (Kyverno/Gatekeeper)
                          └─> Dependency-Track fleet inventory
Enter fullscreen mode Exit fullscreen mode

The synthesis. Monolith: fast, complete remediation + wide blast radius. Microservices: narrow blast radius + slow, incomplete remediation. Monorepo + microservices + golden base is the only combination that buys both. If you're splitting the runtime and staying polyrepo, you're choosing the slow remediation on purpose — write that down in the ADR.

The gate

Split a service out only if you can say yes to at least one trigger and yes to all the mitigations.

Triggers — at least one:

  • Materially different resource profile (CPU vs memory, spiky vs flat)
  • Materially different failure domain (this must survive when that dies)
  • Materially different compliance boundary (this touches PII and that doesn't)
  • A team that needs independent cadence and owns the whole domain
  • Different scaling ceiling or language/runtime, with a real justification

Mitigations — all of them:

  • Callers have timeouts, jittered retries, circuit breakers
  • The service owns its data; no shared schema
  • Contract tests in CI on both sides
  • Distributed tracing before the first split, not after
  • Golden base image with rebuild-on-bump
  • Centralized admission policy and signed images

Not on the list, and never a valid trigger: "the codebase is big." Big is what modules are for.

Verdict

Start monorepo + modular monolith. Enforce module boundaries in CI (import linting, architecture tests) so the seams are real before they're physical. Split one service at a time, strangler-fig style, only against the gate above. Stay monorepo through the split — it's the cheapest insurance you'll ever buy against base-image drift.

Axis Winner Confidence
Version control Monorepo High — few real counterarguments below ~1000 engineers
Updates Monolith High
Availability Monolith, unless calls are async High
Scalability Microservices, iff profiles diverge Conditional
Supply chain Monorepo + microservices + golden base High

Which axis does your org get wrong? I'd guess the one nobody wrote an ADR for.

Top comments (0)