Designing for upgrade: why I default to decoupled architectures

Most production systems are not killed by their first design — they are killed by their inability to absorb the second one. Decoupled architectures let individual components be seamlessly upgraded without rewriting the world.

Most production systems are not killed by their first design. They are killed by their inability to absorb the second one — the new model, the new vendor, the new compliance requirement, the new data source. The question I ask before writing a line of code is rarely “what is the cleanest version of this system today?”. It is “which boundary will move first, and how do I keep the rest still when it does?”.

Boundaries earn their keep

A decoupled architecture is not a free pass to add abstractions. Every boundary you introduce costs serialisation, observability, and cognitive load. The boundaries worth paying for are the ones that align with axes of change: the model layer, the ingestion layer, the identity layer. Get those right and the rest of the codebase can stay deliberately boring.

Artefact twinning over tight coupling

In my PhD work I leaned on what I call “artefact twinning” — a data bridge between a FastAPI backend and a Next.js/Deck.gl frontend, with Redis and PostGIS in the middle. The point was not the specific stack. It was that the model could be retrained, re-served, or swapped without the frontend noticing, because the contract was the artefact, not the runtime.

What this looks like in practice

Infrastructure-as-code, CI/CD, and contract-typed APIs are the unglamorous machinery that makes any of this real. Without them, “decoupled” is just a diagram. With them, upgrading a single component stops being a project and becomes a Tuesday.