FAQ · Architecture

Software Architecture — Frequently Asked Questions

Direct answers on architecture decisions — monolith versus microservices, when to introduce a queue or a cache, how much to document, what a diagram is for, and how to tell a real constraint from an inherited assumption.

Getting started#

What is architecture, as distinct from design?#

The decisions that are expensive to reverse. Which datastore holds the truth, where the boundaries between systems fall, what is synchronous and what is not, what the deployment unit is. Everything cheap to change later is design, and it does not need this level of ceremony.

The practical test: if changing it in a year would take a quarter and touch several teams, it is architecture — write it down.

Should we start with a monolith or microservices?#

Start with a monolith with clear internal boundaries, unless you have a specific reason not to. Microservices trade a local problem — code organisation, which is cheap to fix — for a distributed one: network failure, partial deploys, data consistency across services, and tracing a request through five hops.

The reason to split is organisational or operational, not aesthetic: teams blocking each other on releases, one component with a genuinely different scaling profile, or an isolation requirement. Split when you feel that pain, at the boundary the pain points to.

How do we know where the boundaries go?#

Follow the data and the rate of change. A good boundary owns its data, and things inside it tend to change together. If two components must be deployed in lockstep or share a database table, they are one component wearing two names.

How much architecture should we do up front?#

Enough to avoid the one-way doors. Identify the decisions that are hard to reverse and spend real time on those; make everything else deliberately reversible and decide it later with more information. Time spent designing reversible decisions in advance is usually wasted.

Common decisions#

When do we actually need a queue?#

When the producer and consumer must be decoupled in time — the work can be done later, the consumer may be down, or bursts exceed what the consumer can absorb.

Not to "make things async" in general. A queue adds ordering questions, duplicate delivery, retries, a dead-letter path someone must watch, and a new failure mode where work is accepted and silently never completed. If a synchronous call meets the requirement, it is the simpler system by a wide margin.

When should we add a cache?#

After measuring, and never as the first response to slowness. A cache converts a performance problem into a correctness problem — staleness, invalidation, and behaviour that differs between a warm and a cold system, including on the morning after a deploy.

Fix the query, the index or the N+1 first. If a cache is still needed, decide up front what stale means for this data and how it is invalidated, and write both into the ADR.

Do we need an event-driven architecture?#

Only if something genuinely reacts to events from elsewhere. Event-driven designs are excellent at decoupling producers from consumers and poor at answering "what happened to this order" — that answer becomes a reconstruction across several logs.

If you adopt events, invest early in tracing and in a way to replay. Both feel optional until the first production question you cannot answer.

How do we choose between build and buy?#

Buy anything that is not the thing customers pay you for, unless the integration cost exceeds the build cost or the vendor becomes a single point of failure for your core. Then account honestly for the parts people forget: migration, the shape the vendor forces on your data, and the cost of leaving.

Documentation and diagrams#

What should a diagram show?#

The mechanism — how a request actually flows, where the data lives, what fails when a box dies. Boxes with product names arranged prettily communicate nothing an engineer can act on.

Keep two or three levels at most: system context, containers/services, and one detail diagram for the part that is genuinely intricate. Anything deeper goes stale before it is useful.

How do we stop documentation going stale?#

Write less of it, closer to the code, and make it decision-shaped. ADRs age well because a decision made in March remains true as a historical record even after it is superseded — you add a new record rather than editing the old one. Prose that describes current behaviour rots the moment behaviour changes.

Generate what can be generated. Anything a human must remember to update will eventually be wrong.

Is an architecture review worth the meeting?#

If it happens before the decision, yes. If it happens after implementation, it is a status update with an audience. Review the ADR while the status is still "proposed" and the cost of changing course is a conversation.

Practical judgement#

How do we tell a real constraint from an inherited assumption?#

Ask who would have to approve changing it, and when it was last checked. Real constraints have an owner and a reason: a regulation, a contract, a system with a known migration cost. Assumptions have neither — "we can't use managed services", "everything must be on-premise", "it has to be SQL Server" — and they frequently survive years past the situation that created them.

What is the most common architectural mistake?#

Building for a scale that never arrives, at the cost of the simplicity you need to reach it. The second most common is the mirror image: no seams at all, so the first real growth requires rewriting rather than extending.

The middle path is a simple system with clear boundaries — cheap now, and separable later at the lines you already drew.

When is a rewrite justified?#

Rarely, and almost never for reasons of taste. Justified when the system cannot meet a requirement it must meet and no incremental path exists, or when the platform underneath is genuinely unsupported. Otherwise, strangle it: put the new thing in front, move one capability at a time, and keep shipping. A rewrite trades a working system with known problems for a non-existent system with unknown ones.

Back to Architecture