What "Good" System Architecture Actually Means
Key takeaways
- Ideal architecture minimizes total cost of change, not initial build speed — it's a bet on where volatility will hit over the system's lifetime.
- Core properties that matter: separation of concerns, loose coupling/high cohesion, explicit boundaries, built-in observability, deliberate state management, and failure containment.
- Every architectural choice is a trade-off — simplicity vs. flexibility, consistency vs. availability, latency vs. durability, build vs. depend. There's no universally "correct" side, only the side your constraints justify.
- Patterns (microservices, CQRS, event-driven, etc.) are tools, not goals. Adopt one only if you can name what volatility it isolates, what complexity it costs, and why your system currently justifies that cost.
- Architecture should match team size and maturity, not an aspirational future team (Conway's Law) — a modular monolith often beats premature microservices for small teams.
- A cheap, honest test for "ideal" is operational: can a new engineer understand one module in isolation? Does failure stay local? How far does a changed assumption ripple?
There is no universal blueprint for the "ideal" software architecture — only architectures that are ideal for a specific set of constraints. But underneath that relativism sits a consistent set of properties that separate architectures that age well from those that don't. This piece lays out what those properties are, why they matter, and how they trade off against each other in practice.
1. The Real Definition of "Ideal"
An architecture isn't ideal because it uses the newest pattern or the most sophisticated infrastructure. It's ideal when it minimizes the total cost of change over the system's expected lifetime — the cost of adding features, fixing bugs, onboarding engineers, and scaling load, summed across the system's life, not just at launch.
This reframes the goal: architecture is a bet about which parts of the system will change, and how often. Good architects isolate volatility; bad architects spread it everywhere.
2. The Core Properties
Separation of concerns. Each module should own exactly one responsibility and expose a narrow interface for it. When a change to business logic requires touching the database layer, the presentation layer, and the notification system simultaneously, concerns have leaked across boundaries — and every future change gets more expensive.
Loose coupling, high cohesion. Components should know as little as possible about each other's internals while still being tightly organized internally. This is measured less by whether you use REST vs. gRPC vs. message queues, and more by whether you can replace one component's implementation without any other component noticing.
Explicit boundaries. The interfaces between subsystems — APIs, schemas, contracts — should be deliberate design artifacts, not accidents of whatever happened to be convenient at the time. A system with fuzzy boundaries accumulates hidden dependencies that only surface as production incidents.
Observability by default. An architecture that can't tell you what it's doing at runtime — logging, metrics, tracing baked in from the start — is one where every failure becomes an investigation instead of a lookup. Observability isn't a feature bolted on later; it's a structural property.
Statelessness where possible, explicit state where not. Stateless components scale horizontally almost for free and fail more gracefully. Where state is unavoidable, it should live in a small number of well-defined, well-owned stores rather than smeared across caches, sessions, and local variables.
Failure containment. In any system with enough moving parts, something will fail. The question is whether failure stays local (one degraded feature) or cascades (the whole system falls over). Circuit breakers, timeouts, bulkheads, and idempotent retries exist for this reason — not as extras, but as core structural decisions.
Evolvability over completeness. An architecture that anticipates every future requirement is usually wrong about most of them and over-engineered as a result. The ideal architecture instead makes the next likely change cheap, without trying to predict every change.
3. The Trade-offs Nobody Escapes
Every architectural decision trades one property for another:
- Simplicity vs. flexibility. A monolith is simpler to reason about and deploy; a set of services is more flexible to scale and evolve independently. Choosing microservices before you have the team size or operational maturity to run them is a common way to buy flexibility you can't afford yet.
- Consistency vs. availability. Distributed systems force a choice (per CAP) between strict consistency and continued availability during partitions. There's no universally correct answer — only one correct for your domain's tolerance for stale or unavailable data.
- Latency vs. durability. Writing straight to durable storage is safe but slow; buffering in memory is fast but risky. Most real systems layer both, accepting a small window of risk for a large gain in responsiveness.
- Build vs. depend. Every third-party dependency is an outsourced piece of your architecture's failure modes and change costs. Ideal architecture treats dependencies as long-term commitments, not cheap options.
The mark of a mature engineer is not knowing the "right" answer in the abstract, but being able to state clearly which side of each trade-off the current constraints justify.
4. Patterns Are Tools, Not Goals
Layered architecture, event-driven architecture, microservices, hexagonal/ports-and-adapters, CQRS — these are all means of achieving the core properties above, not ends in themselves. A pattern applied without understanding which property it's buying you (and which it's costing you) tends to produce cargo-cult architecture: technically sophisticated, practically brittle.
A useful test: for any pattern you're considering, be able to name (a) which volatility it isolates, (b) what it costs in operational complexity, and (c) what would have to be true about your system for the trade to be worth it. If you can't answer all three, you're choosing the pattern for the wrong reason.
5. Designing for the Team, Not Just the System
Architecture doesn't just shape how software behaves — it shapes how the team that builds it communicates. Conway's Law states that a system's structure mirrors the communication structure of the organization that builds it. In practice: if your team is small and centralized, a modular monolith with clear internal boundaries will usually outperform a microservices architecture that forces coordination overhead your team doesn't need yet. As the team grows and ownership fragments, boundaries that were internal seams become natural places to split into independent services.
Designing architecture around your actual team size and operational maturity — rather than the team you hope to have in two years — is one of the most consistently underrated decisions in system design.
6. A Working Checklist
When evaluating whether an architecture is heading toward "ideal" for its context, a few honest questions cut through most rationalization:
- Can a new engineer understand one module without reading the whole system?
- If this component fails at 3 a.m., does the failure stay contained, and is there a signal that tells you where to look?
- If a core assumption changes next quarter, how many places does that ripple through?
- Are the boundaries drawn where the business logic actually varies, or where the code happened to be split?
An architecture that scores well on these isn't necessarily impressive — it's just cheap to live with. That, more than any specific technology choice, is what "ideal" actually means.
Subscribe and you’ll hear whenever they publish. Free, like everything here.
One themed piece a day. No spam.
Comments
No comments yet. Sign in to be the first.