Architecture
What Is Software Architecture?
Software architecture is the significant structure of a system — its boundaries, responsibilities, relationships, constraints, and the decisions that are expensive to change. Make those decisions explicitly, reason about trade-offs deliberately, and document why.
What Is Architecture? Software Architecture Patterns in Context
"Architecture is the set of decisions that are expensive to change" is a useful working mental model — not a complete definition. It is memorable because it tells you where to spend your reasoning effort, but it leaves out what those decisions are actually about.
Concretely, architecture is the significant structure of a system: its boundaries (what is one deployable/ownable unit vs another), the responsibilities assigned to each part, the relationships and dependencies between those parts, the constraints it operates under, the quality attributes it must satisfy, and the consequential decisions that follow from all of the above — plus how easily that structure can evolve as requirements change.
Those decisions are driven by four distinct kinds of input, which are easy to blur together but answer different questions:
Requirements vs quality attributes vs constraints vs assumptions
Software architecture patterns, such as a monolith or microservices, are reusable answers to these structural decisions. The visual story below follows one system from a simple start, through growth-driven bottlenecks, to selective decomposition.
Visual Story
The architectural reasoning cycle — applied
Architecture Fundamentals
⤢ Expand for synchronized narration timeline
Scene 6 shows why architecture must be intentionally evolvable. The change trigger in the ADR (Scene 4) is what makes Scene 6 a planned transition rather than a surprise rework.
Problem It Solves
Why explicit architectural reasoning matters
A recurring, costly pattern is architectural decisions made implicitly — without naming the requirements they had to satisfy, the alternatives that existed, or the conditions under which they should be revisited. Not every failure traces back to this, but when a system's architecture quietly stops fitting its requirements, an undocumented decision is a frequent contributor.
When architecture is undocumented, every new engineer re-evaluates every decision from scratch — often re-selecting options that were already rejected for reasons no longer visible. Explicit architectural reasoning creates a shared understanding that survives team changes and organisational growth.
Architecture Models
The fundamental architectural reasoning loop
- 1Identify requirements — What must the system do? What are the quality attributes (availability, latency, consistency, cost)?
- 2Identify constraints — Team size, budget, deadline, existing systems, compliance requirements — hard limits that rule out options.
- 3Enumerate options — List all viable approaches before evaluating any. Do not collapse the option space prematurely.
- 4Evaluate trade-offs — Score each option against the agreed criteria derived from requirements and constraints.
- 5Decide — and document why — Record the decision, the rejected alternatives, and why they were rejected. Include a change trigger.
- 6Acknowledge consequences — Every decision introduces new constraints. Name them explicitly so they can be planned for.
How Architects Think
What experienced engineers know
The best architecture is not the most sophisticated one. It is the one that satisfies the current requirements with the least unnecessary complexity — while remaining evolvable when requirements change.
Principal engineers ask: "What would change my decision?" — this is the mark of conditional reasoning. A cache is right when stale data is acceptable. A queue is right when async processing is tolerable. Multi-region is right when the cost of downtime exceeds the cost of operational complexity.
Architecture is not about knowing the answer. It is about knowing which questions to ask and which constraints matter most.
Illustrative Scenario
A startup grows — and its architecture must evolve (illustrative scenario; the figures are assumed, not measured)
Stage 1: A small team launches a product. A single application server, a single PostgreSQL database. Deploys happen from a laptop. Response time is under 50ms (an assumed figure for this illustration). Life is simple.
Stage 2 — the database is under pressure, but pressure from what? Users grow 10× (also assumed), and the database is visibly struggling. Before reaching for a fix, the first question is which specific resource actually saturated: CPU, read throughput, write throughput, connection count, lock contention, or an inefficient query/missing index that only shows up at this volume. Each has a different correct intervention, and picking the wrong one wastes the migration without relieving the actual pressure.
If the saturation is genuinely read throughput, a read replica helps — but only the read traffic that can tolerate it: replica reads lag behind the primary by a variable amount, so anything that needs to read its own very recent write (e.g. "show the order I just placed") still has to go to the primary or account for that lag explicitly. A read replica does nothing for write pressure, lock contention, or a query missing an index — those need a different fix (see Scalability & Capacity for the full measure-first method).
Stage 3: Traffic becomes globally distributed. Users far from the single region experience materially higher latency. A CDN meaningfully helps static, cacheable assets — but it does not reduce the latency of dynamic API calls or database round-trips, which still cross the same distance. Closing that gap is what multi-region deployment is for, and it is justified by an explicit business or technical requirement (a latency SLA, a data-residency rule, a disaster-recovery target) — not by "users are far away" on its own, because multi-region also introduces real data-consistency and operational cost (see Multi-Region Architecture for when that trade-off is worth making).
The question: Is multi-region justified at this stage? What requirement would make it necessary? What constraint makes it premature today?
Trade-offs
Every architectural decision has consequences
Simplicity vs Independent Scaling
A monolith is simpler to operate but scales as one unit. Microservices let you scale and deploy each boundary independently — but that benefit is not automatic: it only materializes if the split follows real load/ownership boundaries, and it is paid for with distributed-systems and operational cost (network calls, partial failure, more deployables to run).
Consistency vs Availability — under partition
The classic tension only applies while a network partition is actually happening: a distributed system must then choose between serving a request with possibly-stale data (favoring availability) or refusing/delaying it until it can be answered correctly (favoring consistency). Outside of a partition, most systems can and do offer both. See Distributed Architecture for the precise (per-operation, not per-database) version of this trade-off.
Performance vs Cost
More replicas, more caches, more regions improve performance — but each adds infrastructure cost and operational complexity.
Flexibility vs Commitment
Delaying architectural decisions preserves options but can accumulate tech debt. Committing early enables optimization but reduces future adaptability.
What Can Go Wrong
Common architectural failure modes
A choice was made without documentation. Six months later nobody knows why — and it gets reversed, re-introducing the problem it was meant to solve.
The conversation starts from a favourite technology rather than the requirement it must meet, so alternatives are never compared and the wrong fit gets built.
The single-database design that fit the startup stage is still in place after traffic, teams, and data volume have changed, and no one wrote down what signal should reopen the choice. The old architecture quietly becomes the constraint.
Microservices chosen for a 3-person team because "we will need it later." Operational complexity consumes the entire engineering capacity.
Team picks the technically superior option that requires Kafka expertise they do not have. The correct choice on paper becomes the wrong choice in practice.
Every decision requires full consensus. Reversible choices get treated like irreversible ones. Velocity collapses.
"No change trigger" is usually not a one-time mistake to fix — it is an ongoing risk every time a system changes under live traffic. See Architecture Evolution & Migration for how to change a decision safely once it no longer fits.
When To Use Formal Architectural Reasoning
Match the rigour to the reversibility
- → Choosing a primary datastore or data model
- → Defining a public API contract
- → Committing to a platform-level technology
- → Making a decision that affects multiple teams
- → The cost of changing course later is high
- → Choosing an internal library or caching strategy
- → Deciding internal API shapes within a service
- → Making decisions reversible in hours or days
- → The blast radius is bounded to a single team
- → You can validate the choice with a quick spike
A startup's monolith is slowing down feature delivery. A senior engineer proposes a full microservices rewrite. What is the most important question to ask first?
Architecture is the set of decisions that are expensive to change. Make them explicitly, with documented reasoning.
- 1Every architectural decision should be traceable to a requirement or constraint — not a preference.
- 2The reasoning behind a decision matters as much as the decision itself.
- 3Classify decisions as reversible or irreversible before deciding how much analysis they deserve.
- 4Record the change trigger: the condition under which this decision should be revisited.
- 5Good architecture is not the most sophisticated — it is the best fit for this stage.
Key lesson
The most useful sentence in any architecture decision is: 'Revisit this if...' — it makes evolution intentional rather than accidental.