plugins/backend-toolkit/skills/architecture-improvement/SKILL.md
Default to a modular monolith with enforced internal boundaries; treat microservices as a destination after boundaries prove stable, not a starting point. Use when structuring a backend, when tempted to split into services, or when module boundaries blur. Not for the actual schema-split / service-extraction migrations (use migration-strategy + schema-design).
npx skillsauth add jaykim88/claude-ai-engineering architecture-improvementInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Get the benefits of modularity — clear boundaries, independent reasoning — WITHOUT prematurely paying the microservices tax (network latency, deploy multiplication, distributed debugging). Default to a modular monolith; split only when a boundary has proven stable and a real scale/team driver exists.
Universal — the modular-monolith-first decision, enforced internal boundaries, and "extract a service only after the boundary is stable" are architecture principles independent of language.
Default to a modular monolith
Enforce module boundaries inside the monolith
Decouple modules via events where appropriate
async-messaging) let modules react without tight couplingResist premature microservices — they pay off past a threshold most teams never hit
Extract a service ONLY when the trigger is real
Validate (validation loop)
| ❌ Anti-pattern | ✅ Correct | |---|---| | Microservices from day one "for scale" | Modular monolith; split only on a real driver | | Modules sharing DB tables directly | Each module owns its data; communicate via interfaces | | Boundaries by convention only | Enforced (package-boundary check / import lint / module system) | | Extracting a service from a churning boundary | Extract only stable boundaries | | Distributed monolith (services that must deploy together) | Either truly independent services or one monolith |
| Tier | Examples | Action SLA | |---|---|---| | Critical | Distributed monolith (microservices that can't deploy independently — worst of both worlds); cross-service synchronous chains causing cascading failures | Re-architect; high priority | | Major | Unenforced module boundaries eroding; modules sharing DB tables | Fix this sprint | | Minor | Module naming inconsistency; an event that could decouple a still-acceptable direct call | Schedule within 2 sprints |
docs/adr/ADR-NNN-modular-monolith.md — boundaries + enforcement + split criteriarefactor(arch): enforce <module> boundary / docs(adr): modular monolith decisionimports/exports (encapsulation by default)eslint-plugin-boundaries / eslint-plugin-import no-restricted-paths; or Nx module boundaries if in an Nx monorepoimport-linter; Django apps as modulesinternal/ enforcement; clear package APIsasync-messaging — events decouple modules within the monolith before any splitschema-design — module boundaries often align with schema ownershipmigration-strategy — extracting a service means splitting schema ownership safelydevelopment
Design webhooks correctly on both sides — sending (HMAC signing, retries with backoff, at-least-once) and receiving (verify signature on raw body, enqueue + 200 fast, dedupe on event id). Use when adding webhook delivery or consuming a provider's webhooks. Not for internal service-to-service events (use async-messaging) or general outbound-call retry policy (use resilience-patterns).
testing
Use transactions and isolation levels correctly — keep them short, no network calls inside, explicit isolation, retry on serialization conflicts, and choose optimistic vs pessimistic locking. Use when a write spans multiple tables, when concurrent updates corrupt data, or when designing money/inventory flows. Not for cross-service event delivery (use async-messaging Outbox) or schema-level constraints (use schema-design).
development
Backend testing pyramid — unit for pure logic, integration against a real DB (Testcontainers), and consumer-driven contract testing (Pact) for service boundaries. Use before a feature, after a bug fix, or when services break each other on deploy. Not for load testing (use performance-profiling) or security testing (use backend-security-audit).
data-ai
Design a relational schema — normalize to 3NF then denormalize with justification, choose the right Postgres index type per data shape, enforce constraints at the DB. Use when modeling a new domain, when queries are slow, or before a migration. Not for diagnosing slow queries (use query-optimization) or shipping the change without downtime (use migration-strategy).