Designing Maintainable Systems
The architecture that survives is the one that anticipates change, not by predicting the future, but by making change cheap.
The architecture that survives is the one that anticipates change, not by predicting the future, but by making change cheap.
Every design meeting starts with the same question: what should this system look like? The useful follow-up question is rarely asked: what should this system become? Systems are never finished. They are changed, extended, repaired, and occasionally re-architected. The only design goal that matters is making those changes safe and cheap.
Change is the only constant requirement
Feature teams ship to a moving target. Business needs shift. Platforms get deprecated. Users find edge cases no one imagined. If the architecture assumes the requirements are stable, it will be wrong within a quarter.
Maintainable design flips the assumption. It starts from "requirements will change" and asks what shape makes that survivable. The answers recur in every language and every scale:
- Boundaries between subsystems. Code that changes at different speeds should not be tangled together. The seam between them is where change gets absorbed.
- Small, composable pieces. A system built from small pieces can be rearranged. A system built from large ones can only be replaced.
- Explicit contracts. The public shape of a component (its inputs, outputs, and failure modes) is the stable part. Everything behind it is free to evolve.
Coupling is the tax on change
Every dependency is a promise. When component A depends on component B, any change to B risks A. Dependencies are not free; they are a recurring tax, paid on every future change to either side.
The auth service I built was designed around one seam from the start: the token store. Everything that stores or reads tokens goes through a single adapter. When the team moved from a self-hosted store to a managed one, the migration was a single file: one adapter swapped for another, the contract unchanged. The alternative, call sites touching the store directly, would have made that migration a two-week project instead of a two-day one.
The skill is not eliminating dependencies (that's impossible) but placing them deliberately. Couple to stable things: interfaces, data shapes, contracts. Couple to volatile things only through a narrow seam. When the underlying library changes, one adapter absorbs it instead of a hundred call sites.
I look for the places where a change forces touching many files. Each such place is a design smell, not a testament to thoroughness. It means the boundary is in the wrong spot.
Predictable beats clever
There is a kind of architecture that looks beautiful in a diagram and is brutal to work in. Everything is abstracted, generic, and consistent, and nothing is findable. To change one behavior, I must trace through six layers of indirection.
The maintainable system favors predictability over elegance. I want to open a file and guess what it does, from the name, without reading a third of it. That predictability comes from consistency: same shape for same jobs, same conventions, same failure modes. Novelty is the enemy of maintainability. Every time a system does something differently, the next reader pays.
In kit, every command follows the same three-part shape: parse args, load config, run. It is deliberately repetitive. A new command can be written by copying the previous one and changing the middle. Repetition with a consistent shape beats abstraction that hides the shape.
The design document as memory
Architecture decisions are expensive to make and easy to forget. Within six months, no one remembers why the retry logic lives in the gateway, or why the cache is eventual-consistency-tolerant. They only know it is that way, and they change it at their peril.
A one-page design note, written when the decision is made, preserves that knowledge. It does not need to be long. It needs to record: the problem, the chosen approach, the alternatives considered, and the one or two factors that tipped the decision. The auth service's design note is two pages and covers the token format, the session model, and why we rejected the alternative we almost shipped. That page saves more rework than most test suites.
Leave the design better than you found it
Systems decay in small increments: a special case here, a bypass there, a duplicated module over yonder. No single increment is fatal. Their accumulation is.
The counterweight is a design that makes improvement the default. When the seams are clear, moving code is safe. When the tests are fast, restructuring is cheap. When the naming is consistent, finding the right place is easy. A good design is one that invites its own improvement, so the small, daily deposits keep being made.
Takeaway
Design for change, not for the current requirements. Put boundaries where volatility lives, couple to interfaces rather than implementations, and prefer predictable structure over clever abstraction. Write the decision down while it's fresh. A system designed to be improved is a system that will be.