Architecture Decisions That Age Well

June 20, 2024

Architecture decisions that become technical debt share a property: they optimized for the requirements in front of them. The decisions that hold up over five years preserved the ability to change, accepting more complexity at the outset in exchange for lower cost per subsequent change.

That tradeoff is the governing principle, and it is worth stating explicitly because it inverts the usual review criterion. The question at design time is not which structure best fits current requirements. It is which structure is cheapest to move away from when the requirements move.

Technology selection: price the uncertainty

New technology carries an uncertainty premium payable in engineering time, and the premium is only recovered if the technology outlives the system built on it.

The bet has three components: that the maintaining organization or community persists, that the upgrade path stays viable, and that developers who know it remain hirable. When those hold, the choice is sound. When they do not, the team spends years maintaining a dependency the rest of the world abandoned, and eventually pays a migration cost that was not in any estimate.

Practical criteria before adopting:

  • Maintenance concentration. How many organizations depend on it enough to fund its continuation. A single-vendor technology with no adoption outside that vendor's customer base is a different risk than one with broad institutional backing.
  • Upgrade history. Whether previous major versions had migration paths or required rewrites. Past behavior predicts.
  • Hiring surface. Whether the local market contains developers with the skill, or whether every hire is a training project.
  • Reversal cost. What replacing it would require in two years.

This does not argue against adoption. It argues for adopting where the payoff is proportional to the risk — a foundational capability rather than a convenience.

Data schemas: design for the assumption that breaks

Schemas and API contracts cause the most expensive downstream problems, for the same underlying reason: they are consumed by systems outside the team's control, so changing them requires coordination.

The assumptions that break most reliably:

  • Fixed cardinality. One email per user, one address per account, one owner per record. These become many, and the migration is expensive because every consumer assumed one.
  • Structural assumptions about external data. Addresses with a fixed field set, names that split cleanly, identifiers with a stable format. International expansion breaks all of these.
  • Enumerations as constrained columns. Status values enforced by check constraint require a schema migration to add a value, which puts a deployment in the path of a business change.
  • Deletion as removal. Records that need history — anything financial, regulated, or audited — need soft deletion or an event log from the start. Retrofitting history is not possible; the data is gone.

Structures that accommodate change better: JSON columns for genuinely extensible metadata, append-only event records where history is a requirement, and business-meaningful natural keys where the business concept is stable.

API contracts: separate the shape from the store

An API that returns exactly what the database stores couples the schema to every consumer, and the coupling is invisible until the first schema change.

Explicit DTOs at the boundary cost more to write and eliminate that coupling. The database can be restructured with no consumer coordination as long as the projection into the DTO can be maintained. This is the highest-return-per-hour decision in the list, and the one most often skipped because the initial duplication looks unnecessary.

Related contract decisions that age well: additive-only changes to response shapes, explicit versioning from the first release rather than added under pressure, and pagination on every collection endpoint regardless of current data volume.

Boundaries: business capability, not technical layer

Layering by technical function does not produce separation. Controllers calling services calling repositories means every change touches every layer, which is vertical stretching rather than modularity.

Boundaries around business capabilities do produce it. Billing, search, document management, identity — each with its own model, its own data ownership, and explicit dependencies on the others. The properties this yields:

  • A change within a capability touches one area
  • Dependencies between capabilities are visible and reviewable
  • Data ownership is unambiguous, so no two modules write the same tables
  • The seam for later extraction already exists

Most systems need a modular monolith with genuine boundaries rather than microservices. The extraction to separate services remains available if organizational scale eventually requires it. The reverse operation — recovering coherent boundaries from a distributed system that never had them — is substantially harder, because the boundaries are now network calls and changing them requires coordinated deployment.

The review question

At design time, the useful question is what this decision costs to reverse in two years, and whether that cost is written down anywhere.

Decisions with high reversal cost warrant proportionally more analysis. Decisions with low reversal cost should be made quickly and revisited when evidence arrives. Treating both categories with the same process is how teams spend weeks on choices that could be changed in a day, while committing to schema decisions in an afternoon.


Working through a problem like this?

Describe the system and where it's stuck. I'll tell you what the work actually involves.

Get in touch