Andrew Ridgway bec1eaac87
Some checks failed
Test / test (push) Has been cancelled
first pass at the newspaper builder
2026-09-14 11:57:22 +10:00

7.5 KiB

Architecture Patterns

Purpose

Select the right architectural style for your system's requirements. No pattern is universally best — each encodes trade-offs between complexity, scalability, team autonomy, and operational cost.

Pattern Overview

Pattern Best For Team Size Complexity
Modular Monolith Most new projects, small-medium teams 1-4 teams Low-Medium
Microservices Large orgs, independent deployment needs 5+ teams High
Serverless Event-driven workloads, variable traffic Any Medium
Event-Driven Async workflows, loose coupling, audit trails 2+ teams Medium-High
CQRS Read-heavy with complex queries, separate scaling needs 2+ teams High
Hexagonal Testability, swappable infrastructure, long-lived systems Any Medium

Modular Monolith

Description

A single deployable unit with well-defined internal module boundaries. Modules communicate through explicit interfaces, not shared database tables.

When to Use

  • Starting a new product (default choice)
  • Team is small (fewer than 20 developers)
  • Domain boundaries are not yet clear
  • Deployment simplicity is valued

Key Rules

  • Each module owns its data (separate schemas or schema prefixes)
  • Modules communicate via defined interfaces (not direct database queries across modules)
  • Module dependencies are acyclic and explicitly declared
  • Extract to microservices later when boundaries are proven

Trade-offs

  • (+) Simple deployment, debugging, and testing
  • (+) Refactoring across modules is straightforward
  • (-) Scaling is all-or-nothing (cannot scale one module independently)
  • (-) Technology choices are shared across all modules

Microservices

Description

Independently deployable services, each owning a bounded context with its own data store.

When to Use

  • Multiple teams need to deploy independently
  • Different parts of the system have very different scaling needs
  • Polyglot technology requirements
  • Organization has mature DevOps and observability practices

Key Rules

  • Each service owns its database — no shared data stores
  • Services communicate via APIs or events, never direct database access
  • Design for failure: every remote call can fail
  • Deploy independently with backward-compatible API changes

Trade-offs

  • (+) Independent deployment, scaling, and technology choice
  • (+) Team autonomy and clear ownership boundaries
  • (-) Distributed system complexity (network failures, data consistency, debugging)
  • (-) Operational overhead (monitoring, deployment pipelines per service)
  • (-) Integration testing is difficult

Serverless

Description

Application logic runs in ephemeral, event-triggered functions managed by the cloud provider. No server provisioning or management.

When to Use

  • Event-driven workloads (file processing, webhooks, scheduled tasks)
  • Highly variable traffic with long idle periods
  • Rapid prototyping with minimal infrastructure
  • Cost optimization for low or bursty traffic

Key Rules

  • Functions should be stateless — use external stores for state
  • Keep cold start impact low (small bundles, provisioned concurrency for latency-sensitive paths)
  • Design for idempotency — events may be delivered more than once
  • Set concurrency limits to protect downstream systems

Trade-offs

  • (+) Zero infrastructure management, pay-per-use pricing
  • (+) Automatic scaling to zero and to peak
  • (-) Cold start latency (problematic for synchronous user-facing requests)
  • (-) Vendor lock-in to cloud provider's function runtime and event sources
  • (-) Debugging distributed function chains is difficult
  • (-) Execution time limits (15 minutes on AWS Lambda)

Event-Driven Architecture

Description

Components communicate by producing and consuming events through a message broker. Producers do not know about consumers.

When to Use

  • Loose coupling between components is essential
  • Workflows are asynchronous (order processing, notifications, data pipelines)
  • Audit trails and event replay are needed
  • Multiple consumers need to react to the same event

Key Patterns

  • Event Notification: Signal that something happened; consumer fetches details
  • Event-Carried State Transfer: Event contains all data the consumer needs
  • Event Sourcing: Store the sequence of events as the source of truth (not current state)

Trade-offs

  • (+) Loose coupling, independent scalability, natural audit trail
  • (-) Eventual consistency (not all components see changes simultaneously)
  • (-) Event ordering and deduplication complexity
  • (-) Debugging event flows requires distributed tracing

CQRS (Command Query Responsibility Segregation)

Description

Separate the write model (commands that change state) from the read model (queries that return data). Each can use different data stores and schemas optimized for their purpose.

When to Use

  • Read and write patterns are very different (read-heavy with complex projections)
  • Read and write sides need to scale independently
  • Domain model is complex and queries require flattened/denormalized views

Key Rules

  • Commands validate business rules and write to the write store
  • Events propagate changes to the read store (eventually consistent)
  • Read models are disposable — they can be rebuilt from the event stream
  • Accept eventual consistency between write and read sides

Trade-offs

  • (+) Optimized read and write performance independently
  • (+) Read models tailored to specific UI needs without compromising domain model
  • (-) Increased complexity (two models, synchronization, eventual consistency)
  • (-) Overkill for simple CRUD applications

Hexagonal Architecture (Ports and Adapters)

Description

Business logic at the center, surrounded by ports (interfaces) and adapters (implementations). External systems connect through adapters; business logic never depends on infrastructure.

Structure

Adapters (Infrastructure)
  └── Ports (Interfaces)
       └── Domain (Business Logic) ← depends on nothing external

When to Use

  • Business logic is complex and must be testable in isolation
  • Infrastructure may change (swap database, replace message broker, change cloud provider)
  • Long-lived systems where technology choices will evolve

Key Rules

  • Domain layer has zero dependencies on frameworks or infrastructure
  • All external interactions go through port interfaces defined by the domain
  • Adapters implement ports and translate between domain and external formats
  • Tests can use in-memory adapters (no database, no network required)

Migration Paths

From To Approach
Monolith Modular Monolith Identify module boundaries, enforce interface contracts, separate data
Modular Monolith Microservices Extract one module at a time (strangler fig pattern), starting with the least coupled
Monolith Serverless Extract event-driven workflows first (background jobs, file processing)
Microservices Modular Monolith Consolidate when services are too small, team boundaries have shifted, or operational cost exceeds benefit

Decision Framework

  1. Start with a modular monolith unless you have proven reasons not to
  2. Extract to microservices only when team autonomy or independent scaling demands it
  3. Use serverless for event-driven workloads and glue code, not for core request-response APIs
  4. Apply CQRS only when read and write models genuinely diverge
  5. Use hexagonal architecture in the domain layer regardless of the outer architecture style