The right picture, delivered into the wrong rectangle
Resolution, crop and reserved space solve different image problems. Design all three around what the reader needs to see.
An off switch cannot restore a contract that new writes have already changed. Plan reader and writer compatibility across the transition.
The feature flag is off and the old screen has returned. The new records are less cooperative: they still use the representation the new editor wrote. If the old label printer cannot read that shape, the toggle has restored the code path without restoring the service. That is the rollback question I would want answered before the first successful write, while the team can still choose what to preserve.
A feature flag changes a decision in the running software. It does not automatically reverse a data migration, recall a message or restore a deleted field. The distinction matters most during changes that involve more than one deployed component. A web application, a background worker and an export script can live on different release schedules while sharing the same records.
Take a fictional contact system moving from a single address string to structured address fields. The new editor can capture a building, street, town and postcode separately. The old label-printing worker still reads address_text. Turning off the editor may restore the old screen, but it does not tell us what the printer will do with a record saved only in the new shape.
Write down which readers, writers and records the address system supports in each phase. The flag can admit traffic to one of those prepared combinations; it cannot make an untested combination compatible. For rollback, I would start with a new record and ask what the old printer does with it. That small example tells the operator more about the off position than the current Boolean in the flag dashboard.
One approach is to expand the contract before changing the writers. Introduce the structured fields while retaining the existing representation. Update consumers to handle the intended transition rules. Only then allow the new editor to produce records whose treatment has been worked through. Danilo Sato’s description of parallel change calls the stages expand, migrate and contract.
For the address example, the transition reader could prefer a complete structured address when present and otherwise use address_text. “Complete” needs a real definition; not every address fits the same fields. A consumer must not print only the postcode because one newly added property happens to be non-empty. Preserve the original text when conversion is uncertain rather than manufacturing a plausible address from incomplete parts.
List readers beyond the obvious application. A monthly export, an administrative script and a rarely used reprint route can remain dormant during an otherwise convincing rollout. Give each a compatibility status and evidence of its behaviour. A clean production error graph from the new editor does not establish that next month’s export understands the changed record.
While both representations exist, every reader needs the same rule for choosing between them. Keep it near the data-access boundary if that makes it easier to inspect and later remove. Otherwise a populated field can win in one consumer and lose in another. The contact then produces a different label, export or reprint according to which reader happened to run, leaving the migration team investigating several answers to the same address.
Writing both representations sounds like an easy solution, but it creates a consistency problem. If the structured fields and address_text are updated separately, a failure can leave them describing different addresses. Within one database transaction, a shared write can make a stronger local guarantee. Across independent systems, a repairable synchronisation process may be necessary instead.
There is also a semantic limit: structured-to-text rendering is usually easier than lossless text-to-structure conversion. An old editor can change the text in a way that the new fields no longer reflect. The application needs to invalidate, recompute or explicitly review the structured representation after that edit. Silently retaining both turns a transition convenience into two competing sources of truth.
Store conversion provenance where it matters. A structured address confirmed by a person is different evidence from fields guessed by an automated parser. If the old text changes, knowing that distinction helps decide whether to invalidate a conversion or request a new confirmation. Treating all populated fields as equally authoritative hides the most important migration uncertainty.
Decide which writers can run during each phase. Updating all editors to a compatibility release before structured capture may be simplest, but some deployments cannot move together. Then the system needs to recognise older writes and apply the agreed authority rule. Include the delayed worker: its text edit can arrive after another route has confirmed the structured address, and the application has to decide whether those fields remain valid rather than silently keeping both.
Record the relevant flag decision with a request or job. If one step captures an address under the old route and a later step reads a freshly changed flag, the job can straddle two assumptions. Sometimes reevaluation is intended. Sometimes a stable decision for the whole operation is safer. Make that scope explicit rather than assuming a Boolean has the same meaning everywhere.
The address migration’s write-side consistency problem, distinct from reader/record compatibility.
Scroll sideways to see all columns.
| Write path | Risk | Required decision |
|---|---|---|
| Both representations, one database transaction | Partial representation update | Commit the related write together |
| Independent systems | One side may lag or fail | Use repairable synchronisation |
Old editor changes address_text | Structured fields may now describe old address | Invalidate, recompute or explicitly review |
A rollback review should start with a record created by the new editor. Can the old production reader use it? Can the old writer edit it without destroying information? What happens to a queued print job that already contains the new representation? These are more demanding questions than whether the flag service can return false.
For an illustrative release table, old records with old readers are the established case. Old records with transition readers need a fallback test. New records with transition readers need the new-path test. New records with old readers are the dangerous combination: either the release deliberately preserves a compatible text field, or the old reader is no longer an acceptable rollback target.
Once the old representation is removed, some old binaries stop being viable rollback targets. The team may still disable the feature or deploy a forward repair, but restoring the earlier service could require data restoration or a compatibility build. Say where that boundary lies in the plan. The operator should learn what off now means before an incident, including any restoration assumptions that need a rehearsal of their own.
An illustrative address migration from a text field to structured fields. Each supported cell needs a deliberate contract.
Scroll sideways to see all columns.
| Stored record | Old reader | Transition reader |
|---|---|---|
| Old text representation | Established path | Fallback must be tested |
| New structured representation | Only if compatible text retained | New path must be tested |
Pete Hodgson’s feature-toggle article distinguishes different kinds of flags and discusses their carrying cost. A temporary release flag should have an owner and removal conditions. The date is a prompt to check those conditions, not permission to delete the old path regardless of remaining consumers.
Before removing the flag, establish that old writers no longer run, outstanding jobs use a supported shape and required records are migrated or explicitly excepted. Dormant consumers such as the monthly export need evidence too. Then the unused route and its tests can be removed deliberately. Leaving both indefinitely would retain the precedence and compatibility work after the flag has ceased to control a useful release decision.
Retain the useful history: the mapping rules, exceptions and compatibility decision. Remove the accidental complexity of a flag that no longer governs a real choice. This distinction lets the code become simpler without discarding the evidence somebody will need when an unusual old record appears in an export six months later.
Not every flag needs a data-migration programme. A cosmetic variation or a reversible read-only path may have a much smaller compatibility surface. The point is to inspect what persists and who consumes it. The release effort should follow those consequences rather than the number of lines behind the conditional.
Before approving a flag-controlled migration, draw the four reader-and-record combinations and add the writers that can still create each shape. Choose a representative record for every supported cell. Walk a delayed job and an old editor through the overlap. Finally, state what the off position means before and after contracting the data model.
The four reader-and-record combinations give this flag a useful, limited job: choose when traffic enters a supported phase. Turning it off can then lead back to a state somebody has actually examined. If contracting the data removes that state, replace the recovery plan at the same time. The label printer does not care how reversible the release looked in the dashboard; it needs a record the version running today can still understand.
Working on a workflow like this? Hamsa helps finance and operations teams connect their tools and handle the exceptions, starting with one workflow.
See the Payment Operations PilotPrimary sources behind this article. Living documentation may change.

Practical engineering. A human perspective.
More about Hamsa Software and the person behind it. Editorial notes.