The Real Skill Is Not Avoiding Change, But Making Change Reversible

Jaeyeol Lee

Hatched by Jaeyeol Lee

Jun 22, 2026

9 min read

72%

0

When a system starts to live in the world, every change becomes political

The easiest way to build software is to imagine that it lives alone. You edit some files, run some tests, ship some code, and if you do not like the result, you change it again. In that world, a schema is just a set of definitions, a container is just a package, and rollback is a comforting button you can press when reality disappoints you.

But the moment a system reaches production, the fantasy ends. Now one or more codebases depend on your data. Now other services, dashboards, users, batch jobs, and future deploys are all implicitly negotiating with the choices you make today. The technical problem is no longer only correctness. It is coordination across time.

That is the deeper connection between containers and schema growth. Both are often discussed as engineering tools, but they are really answers to the same question: how do you safely change a thing that other things now depend on?

A container image and a database schema are both promises. They say: this is the shape of the world you can rely on. And once that promise has been made to others, the highest form of engineering is not cleverness, it is restraint.

The hidden cost of success: every dependency turns your design into a contract

A startup in its early days can change anything. Rename a field, split a service, swap a runtime, rewrite a table. Nobody notices because nobody else has had time to build a life around your decisions. But success changes the physics.

A growing container system accrues dependencies in obvious ways. A deployment pipeline expects a certain image layout. A monitoring agent expects certain process names, ports, or labels. Security policies, resource limits, and orchestration rules all start to assume that the container behaves in a stable way. What was once an internal implementation detail becomes an external agreement.

Schemas follow the same pattern, but more brutally. A field name is not just a label, it is a public commitment. A table shape is not merely structure, it is a vocabulary that other code has already learned to speak. Rename a field that consumers depend on, and you have not improved the system, you have changed what it means.

This is why the old intuition, “I can always refactor later,” becomes dangerous in production. Refactoring is not free in a living system. It is a negotiation with all the software and people that already interpret your choices. In production, change is never isolated. It is always distributed.

The moment other systems depend on your interfaces, every rename becomes a small act of governance.

That word, governance, matters. It captures what container discipline and schema discipline both have in common: they are not just about building things, but about preventing accidental meaning drift. Reliability is not only about uptime. It is about continuity of meaning.

Why growth and breakage should never be mixed

The most useful mental model here is to separate growth from breakage.

Growth means adding new things: a new container image tag, a new environment variable, a new schema attribute, a new namespace, a new annotation. Breakage means removing something, or changing the meaning of something that already exists. These are not just different degrees of change. They are different species of change.

If you confuse them, you create unnecessary risk. You also make systems harder to reason about, because consumers can never tell whether a familiar name still means what it used to mean. A deployment process that allows both additive rollout and destructive cleanup in the same move is like renovating a building by rearranging the foundation while people are still inside it.

This is why production systems benefit from a strong bias toward additive evolution. In containers, that might mean introducing a new image version alongside the old one, then gradually shifting traffic. In schema design, it means adding a new field rather than repurposing an old one, or introducing a new namespace rather than overloading an existing name.

The principle is simple, but the implications are profound: never make the same symbol do two incompatible jobs. A name once used should remain true to its history, even if you later regret choosing it. If you need a better meaning, create a new name and let the old one retire gracefully.

This feels slower than aggressive cleanup. It is actually faster in the long run because it preserves compatibility. The system keeps moving while the meaning changes safely around it.

Containers and schemas are both about portability, but the deeper portability is semantic

Containers are often sold as a way to make software portable. Package the runtime, dependencies, and app together, then the thing runs anywhere. Schemas, when designed well, do something similar for data. They make data interpretable across codebases, tools, and time.

But the deepest kind of portability is not merely technical. It is semantic portability: the ability for meaning to survive migration.

A container image without a stable contract is not actually portable, because the orchestration layer, observability tools, and adjacent services cannot safely predict how to interact with it. Likewise, a database schema stored as inert text in source control is not truly portable if it cannot be validated, queried, reproduced, and audited where the data actually lives.

That is a crucial insight: the home of a thing matters. A schema kept only as text is like a law written down but never enacted. It may describe intent, but it does not govern reality. When schema lives in the database itself, the database becomes not just storage but the source of truth for meaning, with validation, history, and transaction semantics available as part of the system’s memory.

This is where reproducibility becomes more than an operational convenience. It becomes a method for preserving meaning across time. If you can recreate the environment, replay the transaction, and inspect the history, you are not just testing code. You are testing whether the system’s contract still holds under evolution.

Names are infrastructure: why the right abstraction is not deletion, but layered memory

The instruction to never remove a name can sound extreme until you realize what names do in real systems. Names are how software remembers. They are also how humans coordinate.

Imagine a city that renames its streets every year because planners keep discovering better names. Navigation apps fail, deliveries get delayed, residents get lost, and old records become incomprehensible. The problem is not that better names do not exist. The problem is that names accumulate meaning over time.

In software, namespaces reduce the cost of getting a name wrong because they allow local reuse without global collision. That is a powerful organizational trick. It acknowledges that meaning is contextual. The same word can safely live in two places if the boundaries are explicit.

Annotations and deprecation flags serve a related purpose. They let systems carry their own history. A deprecated field is not trash. It is a marker that says: this meaning once mattered, it still exists, and consumers should migrate away from it carefully. In other words, the system remembers how it grew.

This suggests a better way to think about clean architecture. Clean systems are not those with the fewest leftovers. They are those with the best memory architecture. They preserve old meaning long enough to let new meaning emerge without force.

A mature system does not erase its past. It makes the past legible.

That is why the most resilient software often has a layered structure: old names remain, new names are introduced, namespaces contain drift, annotations preserve context, and migrations are designed to add before they remove. The system becomes a living archive rather than a brittle rewrite.

The real discipline is designing for reversible change

Once you see production as a world of dependencies, a clearer principle appears: the best engineering practice is not “avoid change.” It is make change reversible, and make irreversibility explicit.

Reversible change is additive, parallel, and testable. You add a new container version without deleting the old one. You introduce a new schema field while keeping the old field alive. You validate new behavior in isolation before making it canonical. You preserve the ability to go back, compare, and audit.

Irreversible change should be rare, deliberate, and visible. If you must break compatibility, do it as a named event with a migration plan, not as a side effect of ordinary development. This separation is what protects the organization from confusing evolution with cleanup.

A useful way to think about this is to distinguish three layers of change:

  1. Local change: what you can modify on your own machine without affecting others.
  2. Compatible change: what can be introduced while old consumers continue to function.
  3. Breaking change: what invalidates prior assumptions and requires coordination.

Most engineering failures happen when teams treat layer 3 as if it were layer 1. They act as if the system is still private when it has already become public.

This is also why auditability matters. A system that can explain how it got here is safer than one that merely asserts it is correct now. History is not a luxury. In a changing environment, history is how you know whether change was growth or accidental breakage.

What this means in practice

The practical lesson is not that you should fear refactoring, or that you should never remove anything. It is that removal requires a different process than addition. The more people and systems depend on your design, the more you need explicit choreography.

For containers, that means treating deployment artifacts, runtime expectations, labels, and environment contracts as public interfaces. For schemas, it means treating names as durable commitments, adding rather than repurposing, and using namespaces, annotations, and deprecation to preserve lineage.

The larger mindset shift is this: stop thinking of your system as a set of files, and start thinking of it as a negotiated reality. Once something enters production, it becomes part of a shared world. In that world, the job is not to keep changing faster. It is to change in ways that other participants can survive.

That is a more demanding standard than raw agility, but it is also the only one that scales.

Key Takeaways

  • Separate growth from breakage. Add new things before you remove or repurpose old ones.
  • Treat names as durable contracts. Once a name is in production, assume other code depends on its meaning.
  • Use namespaces and annotations to preserve history. They reduce collision and make change legible over time.
  • Prefer reversible changes. Keep old and new versions alive long enough to validate behavior and migrate safely.
  • Store truth where it is enforced. If schema or runtime contracts matter, keep them in the system that actually governs them, not only in text files or tribal knowledge.

Conclusion: maturity is the art of keeping promises while evolving

The usual story of software maturity says that good systems are stable. But stability is not the absence of change. It is the ability to change without lying to your past users.

That is the hidden unity between containers and schema growth. Both are exercises in preserving trust while the underlying implementation moves. Both ask the same hard question: how do you evolve something without erasing the meaning people already depend on?

The best systems do not win by changing recklessly, and they do not win by freezing. They win by learning how to grow without betrayal. In that sense, engineering maturity is not about making everything new. It is about making change so disciplined that the world can keep trusting you while you build the next version of reality.

Sources

← Back to Library

Hatch New Ideas with Glasp AI 🐣

Glasp AI allows you to hatch new ideas based on your curated content. Let's curate and create with Glasp AI :)

Start Hatching 🐣