Skip to content

Software Architecture

Software architecture is the set of consequential structural decisions that shape how a system is built and changed. It is not a catalog of fashionable diagrams. Good architecture makes important quality attributes achievable while keeping future decisions reversible.

Start with the Forces

Before choosing a structure, write down:

  • business capabilities and critical workflows;
  • expected load, growth, latency, availability, and recovery targets;
  • security, privacy, and regulatory constraints;
  • team boundaries, skills, delivery cadence, and operational capacity;
  • dependencies, legacy constraints, and budget;
  • assumptions that need measurement or experiments.

Architecture is a trade-off. A design cannot maximize simplicity, independent deployment, consistency, availability, performance, and low cost simultaneously.

Module Boundaries

A useful module:

  • owns a coherent responsibility and its invariants;
  • exposes a small, explicit interface;
  • hides implementation and storage details;
  • has high internal cohesion and few outward dependencies;
  • can change without coordinated edits across unrelated modules.

Prefer dependencies that point toward stable domain rules. Cycles, shared mutable state, and a database treated as a public interface make boundaries porous.

Start with the smallest deployment shape that satisfies the constraints. A modular monolith often provides strong boundaries without distributed-systems overhead. Split deployment units only when independent scaling, isolation, ownership, or release cadence supplies measurable value.

Common Structural Styles

Layered Architecture

Groups presentation, application, domain, and infrastructure concerns. It is familiar and works well when dependencies flow downward or inward. Avoid layers that merely forward calls and obscure a simple flow.

Ports and Adapters

Keeps application rules behind ports implemented by adapters such as HTTP handlers, databases, and message brokers. It is useful when the domain must remain independent of frameworks or has several delivery mechanisms. Do not create an interface for every class; add a port at a real boundary.

Event-Driven Architecture

Producers publish facts; consumers react asynchronously. This supports decoupling and independent scaling, but introduces delayed visibility, duplicates, ordering questions, schema evolution, and harder debugging. Events should describe completed facts and carry stable identifiers.

Service-Oriented Systems

Independently deployed services can align ownership and failure isolation with business capabilities. They also add network failure, distributed data, observability, deployment, and compatibility costs. Each service should own its data and invariants; direct writes into another service's database erase the boundary.

Pipes and Filters

Transforms data through independently composable stages. It fits compilers, ETL, media processing, and request middleware. Define ordering, failure, buffering, and back-pressure explicitly.

Plugin Architecture

A stable core exposes extension points implemented by plugins. It fits products that genuinely require third-party or independently released extensions. Version the extension contract and isolate untrusted plugins.

Serverless and Managed Components

Managed runtimes reduce infrastructure work and can scale per request or event. Evaluate execution limits, cold starts, observability, portability, local testing, and cost at sustained load. “Serverless” changes who operates servers; it does not remove architecture.

Integration Patterns

Use patterns to address observed forces, not as default decoration.

Pattern Use when Main cost
Request–response A caller needs an immediate result Temporal coupling
Queue Work can be processed asynchronously Delay, duplicates, backlog operations
Publish–subscribe Several consumers need the same fact Schema and delivery complexity
Outbox A database change and message must agree Relay and deduplication work
Idempotent consumer Delivery or retries can duplicate work Idempotency state and key design
Circuit breaker A failing dependency should be shed temporarily Tuning and additional state
Bulkhead Failure in one workload must not exhaust all resources Reserved capacity may sit idle
Saga A multi-service workflow needs compensating actions Business-specific recovery paths
Strangler fig A legacy system must be replaced incrementally Temporary routing and dual-system cost
Backend for frontend Clients need materially different aggregation Another service to own and operate

Retries require bounded attempts, backoff, jitter, deadlines, and safe operation semantics. A retry without these controls can amplify an outage.

Data Ownership and Consistency

Place invariants with the data and operation that must enforce them. A local transaction is simpler and stronger than cross-service coordination. When data crosses a boundary, define:

  • the source of truth;
  • acceptable staleness;
  • delivery and ordering guarantees;
  • duplicate and conflict handling;
  • reconciliation and repair procedures;
  • retention and privacy rules.

See Databases and Distributed Systems.

Quality Attributes

Quality requirements must be testable scenarios, not adjectives.

Under normal peak load, 99% of checkout requests complete within 400 ms. If the recommendation service is unavailable, checkout continues without recommendations and raises an alert within two minutes.

Typical attributes include:

  • availability and recoverability;
  • latency, throughput, and scalability;
  • security and privacy;
  • correctness and consistency;
  • modifiability and deployability;
  • observability and operability;
  • accessibility and usability;
  • cost and sustainability.

State the stimulus, environment, affected component, expected response, and measurable threshold.

Architecture Decisions

Record consequential decisions in short Architecture Decision Records (ADRs):

# Title
Status: proposed | accepted | superseded

Context: forces and constraints
Decision: what we will do
Consequences: benefits, costs, risks
Alternatives: serious options considered
Follow-up: measurements or revisit trigger

An ADR records why a choice was reasonable at the time. Supersede it rather than rewriting history.

Useful Views

Keep only views that answer real questions:

  • context: users and external systems;
  • containers/deployables: applications and data stores;
  • components: important internal responsibilities;
  • runtime: a critical request, event, or failure sequence;
  • deployment: processes, zones, networks, and dependencies;
  • data: ownership, movement, classification, and retention.

Diagrams need a title, scope, legend, important protocols, and ownership. Store them near the code when possible and review them with the change they describe.

Evaluation

Review an architecture through concrete scenarios:

  1. identify the highest-risk quality requirements;
  2. trace normal, failure, recovery, and change paths;
  3. find sensitivity points and trade-offs;
  4. validate uncertain claims with a prototype or load/failure test;
  5. assign owners and revisit triggers.

Warning signs include shared databases across nominally independent services, synchronous chains without deadlines, unbounded queues, undocumented ownership, manual recovery, and complexity justified only by hypothetical scale.

Practical Checklist

  • Are boundaries aligned with business responsibilities and invariants?
  • Is the simplest viable deployment shape being used?
  • Are critical quality attributes measurable?
  • Are timeouts, overload, partial failure, and recovery designed?
  • Is data ownership explicit?
  • Can changes be deployed and rolled back safely?
  • Can operators observe and diagnose critical paths?
  • Are important decisions and their assumptions recorded?
  • Is there a trigger for revisiting each expensive choice?

Architecture succeeds when the system remains understandable and changeable under its real constraints.