What Makes an Architecture Decision Boring and Defensible
A boring architecture decision uses familiar constraints and well-understood failure behavior instead of adding machinery for hypothetical scale or flexibility. It gives the on-call engineer fewer states to inspect and the design reviewer fewer leaps of faith to accept.
Here is the verdict I want available before a design review drifts into product demos and industry opinions.
| Default choice | Failure prevented | Reason to depart |
|---|---|---|
| One logical writer per mutable record | Conflicting updates and uncertain authority | Explicit conflict resolution for offline-first or active-active writes |
| Idempotent command handlers | Duplicate effects after caller retries | A command whose repeated effects are deliberate and visible |
| Synchronous-first APIs | Unowned intermediate states hidden behind a queue | Work that reliably exceeds the request deadline |
| A familiar storage engine | Recovery delays caused by fragmented operational knowledge | A measured requirement the established store cannot meet |
These defaults can shorten service recovery during a high-severity incident because responders know where writes happen, how retries behave, and which recovery procedure applies. That matters more than an elegant diagram drawn under calm office lighting.
Predictability Wins
Approve added machinery only when the proposal names the current failure it solves and the team that will recover it.
Give Each Mutable Record One Writer
Ownership belongs in the data model. One service or component holds authority to mutate a given record, while any number of consumers may read snapshots, subscribe to changes, or build derived views.
Without that rule, two components eventually encode the same business transition differently. One accepts an update the other rejects. Ordering becomes a matter of timing. During the incident, each team points at a plausible value and nobody can identify the authoritative one.
A single logical writer does not require a single machine. Tenant ID can partition ownership, or a 64-bit record identifier can be hashed across 16 to 64 logical shards. Each shard may run on separate infrastructure while preserving one mutation authority for each entity. This arrangement also removes the need for distributed locks when changing one logical entity.
Where Multiple Writers Earn Their Cost
Multi-writer topologies remain viable for offline-first mobile clients and active-active geographic deployments. The data model must then define conflict resolution directly, using semantics such as CRDTs or last-write-wins timestamps. That is a specific engineering choice with user-visible consequences, not a box labeled “global” on an architecture diagram.
The boundary deserves scrutiny: a transfer, order, or account may span several records even when each record has one writer. This rule narrows the conflict surface; it does not make cross-entity invariants disappear.
Make Every Retried Command Produce One Stable Outcome
The common question is simple: what should a client do after a timeout? The uncomfortable answer is that the client cannot tell whether the server failed before committing or committed successfully and lost the response.
A safe command accepts an idempotency key, stores a durable deduplication record, and returns the original result when the same command arrives again. The transport still offers ordinary delivery semantics. The handler supplies the stable business outcome.
For example, after receiving a 503 Service Unavailable response, a caller can replay the original payload with the identical UUIDv4 idempotency header. The service checks a fast in-memory datastore where keys remain available for 24 to 72 hours. If the first attempt completed, the handler replays its recorded response. If processing continues, the API reports that state instead of launching duplicate work.
The RFC 9110 definition of idempotent methods explains the protocol concept. Application commands still need their own deduplication design because creating a payment, reservation, or deployment often arrives through a method whose business effect requires additional protection.
Four Details the Handler Must Declare
- Scope: State whether the key is unique per account, endpoint, command type, or another explicit boundary.
- Retention: Keep the record long enough to cover credible retries, then define what happens after expiry.
- Payload validation: Reject reuse of a key with a different request body.
- In-progress duplicates: Return a stable processing state rather than running the command twice.
Exactly-once delivery remains a seductive label. Stable command outcomes are the useful part, and they come from stored decisions rather than transport optimism.
Keep the First API Call Synchronous Until the Work Proves Otherwise
A team moved a standard checkout flow onto an asynchronous event bus to separate inventory reservation from payment processing. The diagram looked cleaner. Production supplied the missing details.
For months, engineers chased races that sent success emails for out-of-stock items. The workflow accumulated queues, workers, correlation identifiers, retry policies, status resources, cancellation rules, and dead-letter handling. Its dead-letter queue reconciliation loop became a second checkout system, except this one had no clear owner.
The team removed the queueing infrastructure and returned checkout to a synchronous database transaction with an approximately 2.5-second timeout. The change restored a property users already assumed: the initial response represented the outcome of the attempted purchase.
This recommendation applies where the caller requires an immediate answer and execution reliably finishes within that 2.5-second request deadline. Work exceeding just about 10 seconds deserves a background task with a queryable status resource. At that point, asynchronous execution reflects the workload rather than architectural fashion.
Count the States Before Adding the Queue
Request-response usually exposes success, failure, and timeout. An asynchronous workflow adds accepted, queued, claimed, partially completed, retrying, cancelled, dead-lettered, and reconciled states. Each state needs ownership, observability, and recovery behavior.
Queue Cost Exposed
If reviewers cannot explain who repairs a message after partial completion, the workflow is not decoupled. It is merely harder to trace.
Choose the Storage Engine Your Operators Can Restore
Storage selection should begin with an operational interrogation. Who can change the schema safely? Who understands index behavior? How are backups verified? What happens during failover? Can an engineer reproduce a bad query locally?
A familiar relational or otherwise established store often wins because the team already understands these answers. For a workload under nearly 10,000 transactions per second, maintaining separate graph, document, and time-series clusters can create more consistency work, migration traps, and fragmented developer tooling than the workload justifies.
Product age and launch-date excitement tell us little about recovery. Ask the decisive question instead: after corruption, deletion, or partial failure, how does this system return to a known state?
Make Restore Testing Part of the Choice
Consider a 500GB relational database with point-in-time recovery. The meaningful acceptance test restores it to a secondary instance in under 45 minutes and verifies the recovered data. A slide claiming that backups exist provides no comparable assurance.
Walk the procedure under realistic access controls. Confirm where credentials come from, how the recovery point is selected, when traffic can resume, and which checks establish data integrity. An unfamiliar engine may still prevail, but its advocates inherit the burden of demonstrating that entire path.
Turn Architecture Fashion Into Failure-Mode Questions
Design reviews improve when the room stops debating whether a component is modern and starts tracing what breaks. Use the same sequence for a queue, cache, new database, service split, or cross-region writer.
- Name the concrete failure or constraint driving the proposal.
- Identify the authority that owns each mutable record.
- Describe duplicate delivery and retry behavior.
- Walk through timeouts and every durable intermediate state.
- Demonstrate restore and recovery procedures.
- Record reversibility and the evidence that triggers another review.
That six-point checklist handles the usual objections without turning the review into theatre. “We will need future scale” should produce a threshold and a measurement plan. “Loose coupling” should identify the independent failure and deployment boundaries. “The vendor is modern” should lead to a restore exercise. “One writer is a bottleneck” should prompt a partitioning design before a multi-writer one.
Write assumptions and escape hatches into an Architecture Decision Record. A record might mandate review when write throughput exceeds 5,000 operations per second. The team can then defend today’s simpler software architecture while preserving a concrete route out.
Demand the Trigger
“We may need it later” becomes useful only after “later” has a measurable condition.
Defend Predictable Failure, Not Architectural Nostalgia
Boring decisions earn their place by reducing the ambiguous states engineers must diagnose during a 3:00 AM pager rotation. Familiarity alone is weak evidence. Familiar failure behavior, rehearsed recovery, and explicit ownership form a much stronger case.
Prefer the simplest design whose failure and recovery path the team can explain. Record what would invalidate the choice, including throughput limits, latency boundaries, conflict requirements, and restore targets. This turns conservatism into a testable position rather than a permanent veto.
Architecture diagrams tend to grow busiest around the parts nobody can confidently operate. Consolidate the drawing until authority becomes visible. On that crowded page, the most reassuring number can still be one: one writer for each mutable record.
Your Thoughts
Share your thoughts.
Join the Discussion