| name | audit-backend-architecture |
| description | Read-only audit AND decision advisor for backend/distributed-systems architecture, topology-gated so a Next.js/Supabase monolith and a Kubernetes fleet each see only relevant findings. Reports which patterns are present (maturity matrix, file:line), then — following "start simple, earn every pattern" — recommends which to adopt now, adopt-when-[trigger], or defer as premature, flagging over-engineering and the distributed-monolith anti-pattern. Covers sync request/response vs event-driven, cache-aside, database-per-service, API gateway, BFF, circuit breaker, bulkhead, outbox + CDC, saga, CQRS, hexagonal, strangler-fig, service mesh, cell-based, mTLS, tracing + SLOs, contract testing. Use when "audit backend architecture", "which pattern should I use", "am I over-engineering", "sync vs event-driven", "cache-aside/CQRS/saga/db-per-service", "microservices resilience review", or "audit-backend-architecture". Defers per-call resilience to audit-resilience; fixes via backend-patterns. |
| license | MIT |
audit-backend-architecture — Distributed-Systems Pattern Maturity Audit
Agents ship the happy-path CRUD and call it done. What decides whether a backend survives scale is
the architecture: is there one place that enforces auth/rate-limit/CORS, or is it copy-pasted per
route? Does a payment write and its event commit atomically, or can one succeed while the other fails
(the dual-write problem)? Does a slow dependency trip a breaker and stay in its own resource pool, or
does it exhaust the shared thread/connection pool and take everything down?
This skill works in two lenses:
- Conformance (Phase 2) — which patterns are present, marked Implemented / Partial / Missing /
N/A with
file:line.
- Fit / decision (Phase 3) — for each pattern, Adopt now / Adopt when [trigger] / Defer
(premature) given the codebase's actual stage and pains. The most valuable output is often
"don't build this yet" — the audit is as much a guard against over-engineering as a gap report.
Read-only. This skill assesses and recommends; it does not change code. For the runtime
resilience knobs on individual calls (per-call timeouts, retry backoff+jitter, idempotency keys,
cancellation), defer to audit-resilience — this skill checks whether the patterns exist and
whether they fit, not per-call tuning, and explicitly avoids duplicating it.
Core principle — start simple, earn every pattern
Each pattern solves a specific problem and carries a specific cost. The 2026 consensus is
modular-monolith-first: >90% of systems are well served by a well-structured monolith, and
premature decomposition is the #1 failure mode. A pattern is only "Missing" if the codebase has
the measurable trigger that justifies it — otherwise adopting it is over-engineering, which this
audit flags just as loudly as a real gap.
- Fit beats presence. "No service mesh / no CQRS" is a finding only when a trigger is present.
Never recommend a pattern the stage doesn't warrant.
- Decide per interaction, not per system. Sync vs. event-driven is chosen for each call that
crosses a boundary, not adopted wholesale (see references/patterns.md → Communication style).
- Distributed monolith is worse than a monolith. Services that share a database and must deploy
together pay the distributed tax with none of the independence — call it out explicitly.
- Reversible steps. Prefer changes that keep boundaries adjustable (modular monolith, façade,
outbox) over irreversible bets (premature service split, event-sourcing everywhere).
Phase 0 — Detect topology (gates every later finding)
Architecture advice is only correct in context. Flagging "no service mesh" on a Next.js + Supabase
app is noise; missing it on a 40-service Kubernetes fleet is critical. Classify the repo first,
then apply only the patterns that fit. Never report a pattern as "Missing" if its topology tier is
N/A.
rg -n --hidden -g '!node_modules' -l "vercel\.json|netlify\.toml|next\.config|supabase/config"
fd -H -t f 'Dockerfile|docker-compose\.ya?ml|Containerfile'
fd -H -t d 'k8s|kustomize|charts|helm' ; rg -n "apiVersion:\s*(apps|networking)" -g '*.{yaml,yml}' -l
fd -H -t f 'package.json|go.mod|pyproject.toml|pom.xml|Cargo.toml' | rg -v node_modules
rg -n "kafka|rabbitmq|amqp|nats|sqs|sns|pubsub|kinesis|redpanda|temporal|inngest|trigger\.dev" -l
rg -n "grpc|@grpc|protobuf|\.proto|graphql|apollo|federation|trpc" -l
rg -n "istio|linkerd|consul|envoy|kong|apisig|traefik|nginx-ingress|api-?gateway|ztunnel|waypoint" -l
Record a topology profile and pick the tier:
| Tier | Signals | Patterns in scope |
|---|
| T1 — Serverless / modular monolith | single deployable, Next/Supabase/edge fns, no broker | Gateway concerns (as middleware), BFF-lite, circuit breaker, idempotency/outbox-lite, hexagonal, strangler, tracing/SLO, contract tests |
| T2 — Containers, few services | Dockerfiles, 2–10 services, maybe a broker | + real gateway, BFF-per-client, bulkhead, outbox+relay, saga, backpressure, ACL, mTLS |
| T3 — Kubernetes / microservices / event-driven | k8s manifests, broker, gRPC/federation, 10+ services | + sidecar/service mesh (ambient), CQRS/event-sourcing, cell-based, full saga orchestration, CDC |
If there are no backend services (static site / pure frontend), stop and report reduced
applicability — hand any API-client concerns to audit-fe-api.
Phase 1 — Research current patterns
Follow /research: confirm current-year conventions and the installed libraries for the detected
stack (gateway/proxy, breaker/bulkhead libs, outbox/CDC tooling, saga/workflow engines, mesh mode).
Anchor recommendations to versions actually in the manifest — never propose a library the repo can't
run.
Phase 2 — Architecture maturity matrix
For each in-scope pattern, mark Implemented / Partial / Missing / N/A with file:line, a
one-line "why it matters", and the fix-delegate. Detailed detection commands, "good vs. red-flag"
signals, and fix targets for every pattern are in references/patterns.md —
load it and work through the applicable rows.
| # | Pattern | Tier | This audit checks (structural) | Fix via |
|---|
| 1 | API gateway | T1+ | Auth, rate-limit, CORS, transform, logging, monitoring, caching enforced in one edge layer, not per-route copy-paste | backend-patterns, audit-security |
| 2 | BFF / API composition | T1+ | Per-client shaping vs. one general API; over/under-fetch; who owns aggregation; federation at 5+ teams | backend-patterns, design-api |
| 3 | Circuit breaker | T1+ | A breaker exists around downstream calls (presence/architecture only) | audit-resilience → backend-patterns |
| 4 | Bulkhead | T2+ | Per-dependency resource pools; one slow dep can't exhaust shared pool | backend-patterns |
| 5 | Backpressure / load shedding | T2+ | Queue-based load leveling, bounded queues, shed/429 under overload | backend-patterns |
| 6 | Outbox + relay/CDC | T1+ | Dual-write closed: state + event commit atomically; relay (poll/Debezium) publishes | backend-patterns |
| 7 | Saga | T2+ | Multi-service workflows use local txns + compensation; saga-pivot ordering; orchestration vs choreography | backend-patterns |
| 8 | CQRS + event sourcing | T3 (selective) | Applied only where read/write loads diverge; immutable event log + materialized views | backend-patterns, audit-db-schema |
| 9 | Hexagonal / ports-and-adapters | T1+ | Domain logic isolated from framework/IO behind ports; adapters swappable; testable without infra | workflow-refactor, backend-patterns |
| 10 | Anti-corruption layer | T2+ | 3rd-party/legacy models translated at the boundary, not leaked into the domain | backend-patterns |
| 11 | Strangler-fig migration | any (if legacy) | Incremental replacement behind a façade vs. big-bang rewrite | workflow-refactor |
| 12 | Sidecar / service mesh | T3 | mTLS, retries, L7 policy via mesh; prefer ambient/sidecarless for net-new; not hand-rolled per service | backend-patterns, audit-security |
| 13 | Cell-based architecture | T3 (scale) | Blast-radius isolation, shared-nothing cells, cell-scoped routing/identity | backend-patterns |
| 14 | Zero-trust / mTLS / service identity | T2+ | Service-to-service auth, no implicit trust inside the network | audit-security |
| 15 | Distributed tracing + SLOs | T1+ | OpenTelemetry trace/correlation IDs across hops; RED/USE metrics; SLOs/error budgets | backend-observability |
| 16 | Contract testing | T2+ | Consumer-driven contracts (Pact/OpenAPI) between clients↔BFF and service↔service | test-unit, audit-fe-api |
Communication & data-flow choices (decide per interaction, not per system)
These are selection decisions, not "missing features" — the answer depends on each interaction and
the stage. Detection + trade-offs in references/patterns.md.
| # | Pattern | Tier | This audit checks | Fix via |
|---|
| 17 | Communication style (sync request/response vs async event-driven) | T1+ | Each cross-boundary call fits the interaction: sync for "need the answer now / strong consistency / query"; async for "multiple reactors / independent scaling / background". Symptom to flag: one request making 5 blocking downstream calls | backend-patterns |
| 18 | Cache-aside | T1+ | Hot read-heavy paths (product/profile/config) check cache→DB-on-miss with a TTL/invalidation story; no stale-forever or thundering-herd | backend-patterns, backend-db-performance |
| 19 | Database-per-service / data ownership | T2+ (owned schemas at T1) | Each service/module owns its data; no cross-service table reads. Anti-pattern: shared DB + coupled deploys = distributed monolith | backend-patterns, audit-db-schema |
Rules:
- Evidence or it didn't happen — every verdict cites
file:line or "searched, none found".
- N/A is a first-class verdict — record why (topology tier), don't silently drop a row.
- Don't double-count
audit-resilience's per-call concerns; link to it instead.
Phase 3 — Decide: fit, not just presence
A pattern being "Missing" is only actionable if the codebase has the trigger that justifies it.
For every candidate pattern, classify it into one of three buckets from the detected symptoms:
- Adopt now — a real, present pain maps to this pattern (evidence:
file:line + the symptom).
- Adopt when [trigger] — not yet, but name the concrete signal that should flip it on.
- Defer (premature) — no trigger present; adopting now is over-engineering. Say so plainly.
Maturity ladder (climb only when the rung's trigger fires)
| Stage | Shape | Add these when the trigger appears |
|---|
| 0. Modular monolith | one deploy, clean module boundaries, owned schemas | this is the correct default for most systems |
| 1. Fast & reliable monolith | + cache-aside (read latency/DB load), outbox (dual-write), sync request/response with timeouts + a breaker | reads are hot / an event must not be lost |
| 2. Decoupled | + async event-driven for side effects, BFF per client, CQRS on the one read/write-divergent area | a request fans out to many blocking calls; reads & writes scale differently |
| 3. Distributed | + database-per-service, saga for multi-step transactions, backpressure | a module needs independent deploy/scale; a workflow spans services |
| 4. Fleet | + service mesh (ambient), cell-based, full orchestration | 10+ services, ~50+ engineers, blast-radius/residency needs |
Symptom → pattern (the decision table)
| Observed symptom | Likely pattern | Not this (yet) |
|---|
| Repeated 100ms DB hits on hot read paths | cache-aside | CQRS, read replicas |
| DB write + broker publish as two steps | outbox (+CDC) | 2PC, event sourcing |
| One request makes 5 blocking downstream calls | async event-driven for non-critical hops | full EDA rewrite |
| Reads and writes scale/optimize very differently | CQRS on that slice | event sourcing everywhere |
| Multi-step transaction across services can half-complete | saga + compensation | distributed 2PC |
| Modernizing a legacy system with risk | strangler-fig behind a façade | big-bang rewrite |
| Two teams blocked on one deploy cadence | database-per-service split | service-per-noun |
| Services share tables and deploy together | fix the distributed monolith (own schemas first) | more services |
Over-engineering guardrails (flag these as findings too):
- CQRS / event sourcing where reads and writes don't actually diverge → remove complexity.
- Microservices / mesh / cell-based below the size + differential-scaling threshold → stay modular monolith.
- Wholesale event-driven for simple two-party interactions → a single durable queue (or a sync call) is enough.
- A split that produces a distributed monolith (shared DB, coupled deploys) → worse than the monolith.
Phase 4 — Prioritized report + decision (read-only)
## Backend Architecture Audit — [repo] — [date]
**Topology:** [tier + evidence: N services · broker? · k8s? · transport]
**Stage on the ladder:** [0–4] · **In scope:** [patterns] · **N/A (out of tier):** [list + why]
### Adopt now (present pain → pattern, with evidence)
| Pattern | Symptom (file:line) | Payoff | Fix via |
|---|---|---|---|
| Cache-aside | product page: 100ms DB read per request, no cache (api/products.ts:40) | ~10ms cached reads, DB load ↓ | backend-patterns |
### Adopt when (named trigger, not yet)
| Pattern | Trigger to watch for |
|---|---|
| Database-per-service | orders + billing need independent deploy cadence / separate teams |
### Defer — would be over-engineering now
| Pattern | Why premature |
|---|---|
| CQRS / event sourcing | reads and writes don't diverge; a repository is simpler |
| Service mesh / cell-based | single modular deploy; no 10+-service fleet |
### Critical (data loss / cascading outage / silent inconsistency)
| Gap | Pattern | file:line | Why it bites in prod | Fix via |
|---|---|---|---|---|
| Payment writes DB then publishes to Kafka separately | Outbox (#6) | orders/service.ts:88 | Dual-write: crash between = paid order, no fulfillment event | backend-patterns |
| No breaker/bulkhead around payment provider | #3/#4 | ... | One slow provider exhausts the pool → whole API 503s | audit-resilience/backend-patterns |
### High / Medium (maturity matrix)
| Pattern | Implemented | Partial | Missing | N/A | Fix via |
|---|---|---|---|---|---|
| API gateway | ... | ... | ... | | backend-patterns |
| BFF / composition | ... | ... | ... | | design-api |
| Hexagonal | ... | ... | ... | | workflow-refactor |
### Lift-to-production roadmap (ordered)
1. Close dual-write with Outbox (+ idempotent consumers) → backend-patterns
2. Breaker + bulkhead on every external dependency → audit-resilience → backend-patterns
3. Centralize gateway concerns (auth/rate-limit/CORS/logging) → backend-patterns / audit-security
4. Distributed tracing + SLOs before scaling → backend-observability
5. [T3] Adopt ambient mesh for mTLS/L7 instead of hand-rolled retries → backend-patterns
Forbidden: declaring "production-grade" from passing tests alone; flagging T3-only patterns
(mesh, cell-based, CQRS) against a T1 monolith; re-auditing per-call timeouts/retries that
audit-resilience owns; recommending a saga without compensation logic; recommending CQRS/event
sourcing where read and write loads don't actually diverge; recommending any pattern without naming
its trigger (fit before presence — a "Missing" with no trigger is over-engineering, not a gap);
recommending a service split that yields a distributed monolith (shared DB + coupled deploys).
Related
audit-resilience — per-call runtime resilience (timeouts, retry+backoff+jitter, idempotency keys, cancellation, PII); this audit defers those to it
backend-patterns — implement the architecture: gateway, BFF, outbox, saga, bulkhead, hexagonal (see its references/architecture-patterns.md)
design-api — API contract, versioning, error shapes, pagination
audit-security — auth, CORS, rate-limit-as-abuse-control, mTLS/zero-trust, injection
backend-observability — tracing, correlation IDs, RED/USE metrics, SLOs, PII redaction
audit-db-schema — the data-model side of CQRS / event sourcing / outbox tables / db-per-service
backend-db-performance — cache-aside, query optimization, read replicas for hot paths
workflow-refactor — hexagonal restructure and strangler-fig migration; modular-monolith boundaries
complete-everything — close the audited gaps to done with verification