Software That Still Runs in Ten Years
What keeps software viable for a decade — as an engineering question, not a technology question. For CTOs, technical directors and founders.
What is this? · Reference Guide
A solid guide to an engineering question — with trade-offs, costs and the case in which we decide differently. Not an opinion piece, but a reference text. Go to overview
- Author
- Batunet Engineering
- Reading time
- 17 min
- Level
- Advanced
- Status
- Approved
- Last reviewed
- 21 July 2026
- Updated
- 21 July 2026
On this page
On this page
- Why most software becomes expensive
- What makes software age well
- Observations from long-lived systems
- Architecture decisions that survive
- Why reversibility matters
- The role of testing
- The role of documentation
- The role of operations
- The role of standards
- People move on. Software stays.
- Common mistakes
- When complexity becomes dangerous
- Checklist
- FAQ
- Further reading
Most software is built for the demo, not for the decade. It works on acceptance day and becomes more expensive to change every year after that, until nobody knows why it is the way it is. This text is about the other kind: systems that still run in ten years — and can still be changed safely in ten years.
"Ten years" doesn't mean "unchanged." Software that survives ten years untouched is either trivial or dead. What we mean is something else: the system still runs, it can still be changed at reasonable cost, it is still understandable, and it can still be operated. Longevity is not a property of the code on day one. It is a property of the cost of every change over the years.
That is why the question isn't a technical one. No framework and no language makes software long-lived — the decisions made before them do: where the boundaries lie, what depends on what, what remains reversible and whether the why is preserved.
Why most software becomes expensive
Software is written once and read and changed for a decade. The cost almost never lies in the writing. It lies in the changing — and the most expensive change is the one that has to touch everything at once.
Systems rarely become expensive through a single wrong decision. They become expensive through accumulation: business logic scattered across controllers, models and views; dependencies nobody can keep track of anymore; decisions whose rationale has been lost; failure modes nobody knows about until production reveals them. Each of these small things is harmless on its own. Together, they lead to the point where a small business change becomes a big technical one — and where nobody can say with certainty what else it will break.
The common root is almost always the same: the system was optimized for the launch, not for the decade. The bill arrives later — as change costs that rise every year.
The recommendation is therefore an uncomfortable one: optimize for the cost of change, not for the speed of the first delivery. The price is real. It is slower and more expensive at the start; a system optimized for the launch is finished sooner and cheaper at first. We decide differently for genuine throwaway prototypes, for a market test that is meant to be discarded, and for a hard external deadline whose passing would make the product pointless. There, you optimize for the demo — and plan the rebuild from the outset, instead of letting the prototype accidentally age in production.
| Aspect | Optimized for the launch | Optimized for the decade |
|---|---|---|
| First delivery | sooner, cheaper | later, more expensive |
| Cost of change | rises over the years | stays manageable |
| When the bill arrives | later, as change costs | up front, paid deliberately |
| Right for | prototype, market test, hard deadline | a long-lived system |
Diagram: Coupling determines the reach of a change.
What makes software age well
Software ages well when it stays understandable and changeable. Everything else follows from that. Two forces determine whether this succeeds: coupling and lost context. As coupling rises, every change becomes sweeping. As context is lost, every change becomes a puzzle. Code style, formatting and the choice of language are secondary by comparison.
Concretely, systems age well when a typical change stays local, the load-bearing decisions are few and well-founded, boundaries hold, failure modes are known and a newcomer can still look up the why. These are not questions of style but properties of the structure.
| Property | Ages well | Ages badly |
|---|---|---|
| Typical change | stays local | becomes sweeping |
| Load-bearing decisions | few, well-founded | many, scattered |
| The why | preserved and documented | lost |
| Failure modes | known | only show up in production |
The choice of technology is part of this. We prefer mature technology over novelty — not out of caution, but because its limits are known and somebody will still understand it in five years. The price: you forgo the advantages of newer tools and occasionally write more yourself where a newer tool would have offered convenience. We decide differently when a mature option demonstrably fails to meet a hard requirement — or when the new option has long since become the mature, well-understood choice for this specific problem. "Mature" means understood and maintained, not old and abandoned; that distinction is the whole point.
Observations from long-lived systems
Anyone who accompanies systems over years sees the same patterns recur — regardless of language, industry and team. Not stories, but observations:
- The parts that change most often are rarely the ones you built flexibility into. Flexibility ends up where it is never needed, and rigidity where things have to change.
- A system's real boundaries show up in its incidents, not in its architecture diagram.
- The first thing to be lost is the why; the second is who you could ask about it.
- The database outlives every application layer above it. Schema decisions are the hardest to reverse in the entire system.
Architecture decisions that survive
Not all decisions are equal. Some are load-bearing — they determine where the system's boundaries run, what the domain is, what depends on what and what can be replaced later. These decisions survive when they are few, made deliberately, written down and independent of fashion. The rest are details that can be changed cheaply.
The first load-bearing decision concerns business logic. It belongs in the domain, independent of the framework. The framework is infrastructure — the place where HTTP, persistence and delivery happen — not the place for business rules. The price of this separation is indirection: more code, an abstraction that has to be maintained, and less feature velocity at first because you aren't working directly in the framework. We decide differently for small, short-lived tools and for applications that at their core are nothing but the framework — a pure CRUD interface, for example. There, coupling to the framework is not a mistake but appropriate simplicity.
Diagram: Dependencies point inward; the domain depends on nothing.
The second concerns how the system is cut. Our default is the modular monolith: one deployable, clear module boundaries inside. The price compared with distributed services: you can't scale teams and parts independently, and a poorly delimited module can affect others; module boundaries only hold through discipline, because nothing enforces them technically. We decide differently when there are real, lasting reasons to split — independent scaling of individual parts, organizational boundaries between teams, regulatory isolation. Then we cut, but along those real boundaries, not on principle.
What makes these decisions survive is less their correctness than their visibility: they are made explicitly and with a rationale, not as a byproduct. A load-bearing decision that nobody has recognized as such is the most dangerous kind — because nobody is guarding it.
Why reversibility matters
We don't predict the future; we keep change cheap. That isn't modesty but the only stance that holds up over ten years, because nobody today knows tomorrow's requirements. Instead of betting on the right prediction, we design so that a wrong assumption is cheap to correct.
The key is distinguishing between decisions that are easy to take back and those that aren't. An easily reversible decision is made quickly and corrected if needed. One that is expensive to reverse — a public data format, an external contract, the choice of persistence model — is made slowly, and the why gets written down.
The recommendation: when in doubt, choose the reversible option and encapsulate the irreversible ones behind boundaries so that their reach stays limited. The price is that a reversible design is rarely optimal for today's case — an interface you may never need, or a boundary that costs an extra call. We decide differently for a proven, stable requirement that won't change — a mandated format, a fixed legal rule. There, the abstraction is waste; you commit directly.
Diagram: Reversible things quickly; irreversible things encapsulated and justified.
Reversibility has a second half that is often forgotten: the recorded why. A decision whose rationale has been lost cannot be safely taken back, because nobody knows anymore what it originally weighed. That is why writing down the why isn't paperwork but part of reversibility itself.
The role of testing
The purpose of tests is not to reach a coverage number. Their purpose is to make change safe. A test's value is measured by the change it enables — not by the fact that it exists. High coverage over trivial code buys nothing; a single test over the most expensive rule buys peace of mind with every later change.
That is why we test where mistakes are expensive: in the business core and on the paths whose breakage causes real damage. We prove the hardest path early and end to end, before the breadth is built — risk is cleared out at the start, not discovered at the end.
The price is that every test is itself code you maintain. Too much of it slows change and cements a design you might still want to discard; too little turns every change into a test of nerve. We decide differently for throwaway code and exploratory spikes whose outcome is still open — no testing there yet, because it isn't clear yet what will stay. As soon as something stays and is expensive to break, the test comes.
The role of documentation
The code says what the system does. It almost never says why it was built this way and not another. That very why is the first thing to be lost, and its loss turns every later change into guesswork. Durable documentation is therefore not the complete kind but the kind that records decisions and the trade-offs behind them.
The recommendation: document decisions and their trade-offs — short decision records — not every function. The price is time, and documentation can go stale; wrong docs are worse than none, because they mislead. We decide differently for trivially reversible decisions: what costs nothing to change isn't worth an entry.
The consequence is not omission but focus: we document the slow-moving things — decisions, boundaries, operations — not the implementation details that change constantly anyway. Documentation about fast-moving things is wrong the day after it is written; about slow-moving things, it lasts for years.
The role of operations
Software doesn't end at launch. A system meant to run for ten years will be operated for ten years — and operations help decide whether it gets there. A system whose failure modes are unknown isn't finished; it just hasn't failed yet.
Two things therefore belong at the beginning, not in the first incident. First, observability: you have to be able to see from the outside what is happening inside before something goes wrong. Second, designing for failure: you rehearse how the system behaves when a dependency fails, before production forces the answer.
Diagram: Observability shows the signal before the threshold turns into an outage.
The real test isn't the quiet day but the incident at three in the morning, handled by someone who didn't build the system. Under pressure, nobody rises to the level of their design; you fall to the level of what has been prepared. Four things then decide the outcome: whether you can see what is happening (observability); whether the damage stays contained instead of spreading (isolation, small blast radius); whether the last change can be rolled back quickly (reversible delivery); and whether there is a rehearsed path instead of improvisation. Because most outages follow a change rather than load, the fastest recovery is almost always to undo the change — which requires changes to be small and reversible.
The recommendation: build in observability from day one and design for the person who operates the system under pressure — clear signals, small blast radius, fast rollback, rehearsed failure scenarios. The price is effort that produces nothing on a good day: instrumentation, rollback machinery, rehearsed outages, some latency. We decide differently for systems that can tolerate outages — an internal tool, for example. There, the depth may be lower, but the signals stay; even an internal tool has to be understood when it jams at three in the morning.
Delivery itself is part of this: small, reversible steps, without downtime where availability matters. The price is more delivery machinery; we decide differently where a short maintenance window is acceptable and simplicity tips the balance.
The role of standards
Standards are shared conventions — widespread formats, well-known interfaces, boundaries that outlast any individual. Their value for longevity is twofold: they lower the cost for a newcomer to understand the system, and the cost of replacing a part. You can still hire for a system made of well-known building blocks in five years; a system made entirely of custom solutions has to be learned all over again.
The recommendation: prefer boring, widespread interfaces and conventions — HTTP, SQL, established formats — over custom solutions. The price is that a standard rarely fits perfectly and a tailor-made solution can be more efficient for the exact case; you accept some inefficiency and a certain constraint. We decide differently at a proven bottleneck where the standard is measurably the problem — there, you optimize locally and behind a boundary, not across the whole system.
Consistency within a codebase is also a standard. A rule applied the same way everywhere is worth more than the best individual solution at each spot, because it keeps the system readable as a whole.
People move on. Software stays.
Over ten years, a team turns over completely. The people who built a system are gone long before the system is — and with them the knowledge that existed only in their heads. Understandability is therefore not a courtesy to successors but the precondition for software outliving its authors.
The test is harsh: can a competent stranger make a change safely — from the code, decisions, boundaries and operations alone, without any of the original authors? If the answer is "only if you ask X," the system depends on a person, not on itself. Then every departure becomes a risk and every new hire months of archaeology.
The recommendation: write for the reader who isn't there yet — things named after what they do, the why recorded, boundaries explicit, cleverness only where it explains itself. The price is that this is slower than writing for yourself and seems redundant as long as the original team is still around. We decide differently only for short-lived code whose author remains its only reader. Everything meant to make it through the decade is written for strangers.
Common mistakes
The same patterns make software expensive prematurely, again and again:
Optimizing for the launch instead of for change costs. Business logic in controllers and models instead of in the domain — every business change becomes a technical one. Premature distribution: microservices and abstractions as the starting point instead of as the answer to a concrete problem. Coupling to the framework without need. Choosing technology by fashion instead of by context and lifespan. A lost why, because decisions were never recorded. And getting testing wrong in both directions at once — testing everything and cementing the design, or testing nothing and turning every change into a test of nerve.
None of these mistakes is visible on day one. All of them become visible later — as change costs nobody planned for.
When complexity becomes dangerous
Complexity arises on its own. Simplicity has to be designed. That makes the direction of caution clear: we don't add structure before it is needed, and we don't remove structure that is load-bearing.
Complexity becomes dangerous when it doesn't pay for a real problem — when it is built in for a future that may never come: an abstraction for the second use case that doesn't exist yet, a distributed system for an unmeasured load, an interface for a vendor switch nobody is planning. Each of these anticipations permanently costs understandability and only pays off if the presumed future arrives.
The recommendation: add complexity only against a present, named problem. The price of this restraint is that you occasionally have to retrofit later under pressure — introducing a boundary after the fact costs more than having had it from the start. We decide differently when the future need is all but certain and cheap to build in now: a known second market, a contractually committed integration. Then you put the seam in early — but against something concrete, not against a hunch.
Checklist
Questions a CTO can ask about their own system. They are diagnostic questions, not verdicts — the answers tell you where a system stands, not whether it has "passed."
- Does a typical change stay local, or does it propagate? Propagation is the first sign of excessive coupling.
- Can we still name why the load-bearing decisions were made the way they were? If the why is gone, every change is a puzzle.
- Is the business logic independent of the framework? Otherwise every business change becomes an infrastructure change.
- Which decisions are irreversible — and are they encapsulated? Irreversible decisions without a boundary are the biggest risk.
- Are the paths that are expensive to break tested — and only those? Both too much and too little make change hard.
- Are the failure modes known and rehearsed? An unknown failure mode is an outage that hasn't happened yet.
- Could a new experienced person understand the system from what has been written down? If not, it depends on people's heads.
- Is there complexity without a present problem that pays for it? Such complexity is pure loss.
- Do we operate what we have built — with observability from day one? Operations help decide the ten years.
- Could a part be replaced without rewriting the whole? Replaceability is the practical test of the boundaries.
FAQ
Does long-lived mean you stop changing things? On the contrary. Long-lived means change stays cheap. A system you can no longer touch isn't long-lived but frozen — and frozen systems get replaced as soon as the world moves on.
Isn't mature technology a risk? Only if you confuse mature with old and abandoned. Mature means: understood, maintained, with known limits. If anything, the risk lies the other way around — with new technology whose limits nobody knows yet and that nobody may be maintaining in five years.
Doesn't this simply cost more? At the start, yes. Over the decade, no, because change costs don't run away from you. But the honest answer comes with a condition: if the software isn't meant to live through the decade at all, don't pay for it. For a throwaway prototype, longevity is the wrong investment.
Are microservices the path to longevity? Not inherently, and for mid-sized systems often the opposite. Distribution adds operational and coupling costs that only a concrete problem — independent scaling, organizational or regulatory boundaries — justifies. Without that problem, distribution tends to shorten a system's lifespan rather than extend it.
How much documentation do you need? As much as necessary to preserve the why of the slow-moving things — decisions, boundaries, operations. No more. Documentation about fast-moving implementation details is wrong the next day and then does harm.
Can you guarantee that software will run for ten years? No, and anyone who guarantees it hasn't understood the question — it can't be guaranteed, because the world changes. It can be designed for, and you can name what would break it: a lost why, sprawling coupling, unknown failure modes, complexity without a problem. We build against these four. That's no guarantee, but it is the difference between hope and engineering.
Further reading
This text is the foundation; the following go deeper into individual decisions from it:
- Reversibility over Prediction — how to make decisions under uncertainty when you don't know the future.
- The modular monolith — when it is the right choice, and when it isn't.
- Domain logic independent of the framework — why business rules don't belong in the controller.
- Testing where mistakes are expensive — a testing strategy without dogma.
- Observability from day one and Designing for failure — operations as part of the design.
- Mature Technology over Novelty — why we don't follow fashion.
- Laravel or Symfony — an honest architecture comparison as a concrete example of a load-bearing technology choice.
Everything rests on the Batunet Manifesto and the Batunet Engineering Method.
The test for long-lived software is easy to set and hard to pass: could someone who wasn't there today safely change the system in five years? If so, the work was good. You won't be able to tell by looking at it — it just runs.
Referenced entities
Where this guide fits on the path.
Continue your engineering journey.
Related concepts, decisions, playbooks and perspectives — as one connected path, not a list of links.
Technologies
Engineering decisions
A concrete project in this field?
Reference Guides show how we think. For your system, talk to our management — technical, no sales pitch.
