// architecture

Software Architecture Patterns

A practitioner's tour of the architecture patterns that show up in real backend systems. Each pattern explains what it is, when to use it, the trade-offs, and how it composes with the others.

Quick Reference

  • Monolith — one deployable; fastest to ship, hardest to scale teams
  • Modular Monolith — bounded modules in one process; best default
  • Microservices — independent services; high ops cost, high team scalability
  • Event-Driven — async via events; decouples producers from consumers
  • Hexagonal / Ports & Adapters — domain at the center, IO at the edges
  • Clean Architecture — concentric layers; dependencies point inward
  • CQRS — separate read and write models
  • Event Sourcing — store events; derive state by replay

Learning Path

Recommended order

  1. 1.Beginner
  2. 2.Intermediate
  3. 3.Advanced

Prerequisites

  • Built at least one end-to-end app
  • Understand HTTP, DB, queues at a high level

Skills you will learn

  • Reasoning about coupling and cohesion at the system level
  • Choosing the right pattern for team size and load
  • Composing patterns (e.g., modular monolith + CQRS)

Estimated time

2–3 hours to read; years to master in practice.

Architecture Overview

Architecture

Hexagonal (Ports & Adapters) Architecture

DRIVING ADAPTERSINPUT PORTSDOMAIN COREOUTPUT PORTSDRIVEN ADAPTERSREST ControllerCLI / SchedulerUse Case PortinterfaceDomain ModelEntities · ServicesRepository PortinterfaceEvent PortinterfaceJPA AdapterPostgreSQLKafka AdapterEvents
The domain core depends on nothing. Inbound adapters drive it through input ports; outbound adapters implement output ports to talk to the outside world.

Monolith

One deployable, one database, one process.

Recommended

All features in a single codebase and runtime. Simple to develop, deploy, and reason about — the right default for small teams and early-stage products.

Pros

  • +Fastest to ship
  • +Easy local dev
  • +Atomic transactions
  • +One observability stack

Cons

  • Coupled deploys
  • Hard to scale teams past ~15 engineers
  • Single-language lock-in

Best for: MVPs, small teams, products with unclear domain boundaries.

Modular Monolith

Bounded modules behind hard interfaces, one runtime.

Split the codebase into modules with clear, enforced boundaries (no cross-module DB access). You get most of the dev-velocity of a monolith with a clean upgrade path to microservices.

Pros

  • +Strong boundaries without ops cost
  • +Refactor inside a module freely
  • +Easy upgrade path

Cons

  • Requires discipline
  • Boundaries decay without tests

Best for: Teams of 5–25 engineers wanting structure without distributed-system tax.

Microservices

Independently deployable services around business capabilities.

Each service owns its data and lifecycle. Enables team autonomy at scale, at the cost of distributed-system problems (latency, consistency, observability).

Pros

  • +Independent deploys
  • +Per-service tech choices
  • +Team autonomy at scale

Cons

  • Distributed-system complexity
  • Heavier ops
  • Eventual consistency by default

Best for: Organizations with 30+ engineers and clear bounded contexts.

Event-Driven Architecture

Services communicate via asynchronous events.

Producers emit events to a broker (Kafka/RabbitMQ); consumers react. Loose coupling, natural fan-out, but harder to reason about end-to-end flow.

Pros

  • +Loose coupling
  • +Natural retry/backpressure
  • +Audit trail of intent

Cons

  • Eventual consistency
  • Hard to debug across consumers
  • Schema governance is critical

Best for: High-throughput pipelines, cross-team integrations.

Hexagonal (Ports & Adapters)

Domain at the center, IO at the edges.

The domain knows nothing about HTTP, DB, or queues. Adapters translate between the outside world and domain ports.

Pros

  • +Easy to test the domain in isolation
  • +Swap infra without touching business logic

Cons

  • More boilerplate up front

Best for: Long-lived business systems with non-trivial rules.

Clean Architecture

Concentric layers, dependencies point inward.

Entities → Use Cases → Interface Adapters → Frameworks. Frameworks depend on use cases, never the other way around.

Pros

  • +Framework-independent core
  • +Testable use cases

Cons

  • Layer overhead in small apps

Best for: Apps you expect to outlive their framework.

Layered Architecture

Classic controller → service → repository.

The default in most Spring/Django/Rails apps. Good enough for most CRUD-heavy services.

Pros

  • +Familiar
  • +Easy to onboard

Cons

  • Anemic domain model risk
  • Cross-cutting concerns leak

Best for: Most CRUD-heavy backends.

CQRS

Separate read model from write model.

Commands mutate state; queries hit denormalized read models optimized for the UI. Often paired with event sourcing.

Pros

  • +Reads scale independently
  • +Write model stays clean

Cons

  • Eventual consistency between read and write
  • More moving parts

Best for: Read-heavy systems with complex query shapes.

Event Sourcing

Store every state change as an immutable event.

State is derived by replaying events. Powerful for audit, debugging, temporal queries — but a steep learning curve.

Pros

  • +Full audit trail
  • +Time-travel debugging
  • +Natural fit with CQRS

Cons

  • Schema evolution is hard
  • Operational complexity

Best for: Banking, ledger, trading, regulated domains.

Pattern Selection Matrix

PatternTeam SizeOps CostBest Trade-off
Monolith1–10LowSpeed of delivery
Modular Monolith5–25Low–MedStructure + speed
Microservices30+HighTeam autonomy
Event-DrivenAnyMed–HighLoose coupling
HexagonalAnyLowTestability
CQRS + ES10+HighAudit + read scale

Common Mistakes

  • !Reaching for microservices before you've felt monolith pain.
  • !'Distributed monolith' — services that must deploy together.
  • !Event-driven without schema governance (Avro/Protobuf + registry).
  • !Hexagonal architecture as ceremony — layers without real adapters.

Production Tips

  • Start as a modular monolith; extract services only along proven seams.
  • Define bounded contexts before drawing service boundaries.
  • Pair event-driven flows with the Outbox pattern for atomic publish.
  • Document one Architecture Decision Record (ADR) per significant choice.

Further Reading

Frequently Asked Questions

Should I start with microservices?

No. Start with a modular monolith and extract services only when team or scale requires it.

Is CQRS overkill for CRUD apps?

Yes — use it when read and write shapes meaningfully diverge or read load dwarfs write load.

Hexagonal vs Clean Architecture?

They're close cousins. Hexagonal emphasizes ports/adapters; Clean adds explicit layering. Pick one vocabulary per team.