Ground Truth

Put the Catalogue in Data and Growth Becomes a Row Instead of a Release

Mostafa DhouibMostafa Dhouib·7 min read
The short answer

Every product has one thing certain to grow: the list of cities, currencies, carriers, document types or tenants it supports. Where that list lives is decided on day one and cannot be revisited cheaply, because by the time it matters the launch set is wired through the application. Modelled as data, adding to it is a row. Modelled as code, it is a ticket, a deployment and a regression pass, forever.

Put the Catalogue in Data and Growth Becomes a Row Instead of a Release

The short answer. Nearly every product has a catalogue: the set of cities, currencies, carriers, jurisdictions, document types or tenants it handles. Whether that catalogue is data or code is decided implicitly in the first week, and it is one of the few architectural choices that cannot be revisited cheaply, because by the time the second entry is needed the first one is woven through the application. As code, every addition is an engineering ticket, a deployment and a regression pass, which puts a permanent tax on the one axis the business most wants to move. As data, it is a row. One product shipped 88 airports at launch and expanded its corridor set with zero rewrites, because the decision was made before the first corridor rather than after it.

The pressure at the start of a build is entirely toward hard-coding. There is one launch market, one currency, one document type. Generalising for a second case that does not exist yet is speculative, and speculative generality is a well-known way to waste a quarter.

So the launch case goes in directly, which is defensible, and the cost arrives later in a form nobody attributes to that decision.

What hard-coding actually costs

Not the first addition. The first one is easy, because the code is fresh and somebody remembers how it works.

The cost is structural and it compounds.

Every addition is an engineering transaction. A ticket, a developer, a review, a deployment, a regression pass. The business wants to add a market and the answer is a sprint. That is a tax on growth, levied on the axis the business cares most about.

The knowledge sits in the wrong place. The person who knows what the new market requires is in operations or commercial. The person who can change it is an engineer. Every addition is a translation between them, and translations lose things.

The launch case becomes an assumption. Hard-coded values do not stay in one file. A currency assumption reaches formatting, rounding, validation and reporting. The second currency is not one change, it is an archaeology project across every place the first one leaked.

Testing degrades. With the catalogue in code, tests are written against the launch case, so they pass for reasons that will not hold for the second entry.

Catalogue in code
Adding an entry
a ticket and a release
a developer, a review, a deployment and a regression pass, every time, forever
Who knows what the entry needs
not who can change it
operations knows the requirement, an engineer owns the file, and every addition is a translation that loses things
The launch case
becomes an assumption
a currency assumption reaches formatting, rounding, validation and reporting, so the second currency is an archaeology project
The tests
pass for the wrong reason
written against the launch case, so they hold for reasons that will not hold for the second entry
Catalogue in data
Adding an entry
a row
carrying every parameter that varies, not just an identifier and a display name
Effective dates
required
without them you cannot reproduce what the system did last month, which is exactly what a disputed old result needs
Provenance
required
where the entry came from and when it was last confirmed, because a stale row still returns an answer
Validation on write
and a review path
otherwise a bad row deploys instantly and you have swapped a slow safe process for a fast dangerous one
decided implicitly in the first week
The useful test is whether adding an entry of the same kind requires touching code. If anything branches on the identifier, the catalogue is in data with a code appendix, and the appendix is where the next change goes.
FigureThe first addition is easy, because the code is fresh and somebody remembers how it works. The cost is structural and it compounds, and it lands as a tax on the one axis the business most wants to move. One product shipped 88 airports at launch and expanded its corridor set with zero rewrites, because the decision was made before the first corridor rather than after it.

What "as data" requires

Putting the catalogue in data is not just a table. The entries have to carry everything that varies, which is where it actually gets designed.

For each entry: its identifier, its display name, and then every parameter that differs. Thresholds, rules, formats, which optional behaviours apply. If any of those are still in code, branching on the identifier, the catalogue is not in data. It is in data with a code appendix, and the appendix is where the next change goes.

The useful test is whether adding an entry requires touching code. If it requires a code change for any entry of the same kind, the modelling is incomplete.

Two more things earn their place immediately.

Effective dates. Entries change. A rule is corrected, a threshold moves, a market's requirements shift. Without dates, you cannot reproduce what the system did last month, and reproducing past behaviour is exactly what you need when somebody disputes an old result.

Provenance. Where did this entry come from and when was it last confirmed. A catalogue that cannot say how old an entry is degrades silently, because a stale row still returns an answer.

The boundary it does not cross

Data-driven expansion covers entries of the same kind. This is the limit worth stating clearly, because over-promising here is how the approach gets discredited.

A new airport is the same kind of thing as the existing airports: it has a location, buffers, rules of types the engine already understands. A row.

A genuinely new itinerary structure, say multi-hub chains rather than a single intermediate point, is not a new entry. It is a new shape the engine does not model, and it is an engine change. Calling that a configuration row would be wrong, and pretending the catalogue covers it is how a team ends up with a configuration language that is secretly a programming language nobody can debug.

The honest statement is: entries of the kinds we model are rows, new kinds are engineering. Then the question for any request becomes which of the two it is, which is a quick and useful conversation rather than an estimate.

The gate
Is this a new instance of a kind the engine already models?
A row
A new airport has a location, buffers and rules of types the engine already understands.
An engine change
Multi-hub chains rather than a single intermediate point is a shape the engine does not model. Calling that a configuration row produces a configuration language that is secretly a programming language nobody can debug.
Model the catalogue as data from the start and validate the engine deeply against one entry. The structure supports many, the validation is deep on one, and that is the combination that holds.
FigureThe limit worth stating plainly, because over-promising here is how the approach gets discredited. Entries of the kinds you model are rows and new kinds are engineering, and once that is said out loud every request becomes a quick classification rather than an estimate.

Prove the engine narrow first

The complementary move, and the one that keeps this from becoming speculative generality.

Model the catalogue as data from the start, then validate the engine against a single entry. One corridor, one market, one document type, where the behaviour can be checked against reality in detail.

This is the opposite of building for many and testing with one. The structure supports many and the validation is deep on one, which is the combination that holds. You find out whether the engine is right where you can actually tell, and expansion is configuration rather than code because the structure was there from the beginning.

The cost of this over hard-coding is small, measured in days at the start. The cost of the reverse is measured in the first expansion and every one after it.

Where this applies

Markets and jurisdictions, currencies and tax rules, carriers and service levels, document types and extraction rules, tenants and their configurations, languages and locales, product categories and their validation rules, integrations and their adapters.

The test for any of them: is there a list the business will want to extend, and is extending it currently an engineering task? If yes to both, that list belongs in data, and the longer it stays in code the more expensive moving it becomes.

The honest limit

A data-driven catalogue moves work from engineering to operations, which is only an improvement if operations can actually do it. A table nobody outside the team can safely edit is a hard-coded catalogue with extra steps and a worse failure mode, since a bad row deploys instantly with no review.

So the catalogue needs validation on write, a review path for changes that matter, and ideally a way to test an entry before it goes live. Otherwise you have replaced a slow, safe process with a fast, dangerous one, and the first bad row will be an incident rather than a failed build.

FAQ

When should I move a catalogue from code to configuration? Before the first entry ships, because afterwards the launch case has leaked into formatting, validation, rounding and reporting, and the second entry is an archaeology project rather than a change. The decision is cheap on day one and expensive to revisit.

Isn't building for a second case speculative generality? Modelling the catalogue as data is not the same as building features for cases you do not have. The structure costs days at the start and you still validate deeply against one entry. Speculative generality is building the second case. This is declining to weave the first one through the application.

How do I know whether my catalogue is genuinely in data? Ask whether adding an entry of the same kind requires any code change. If anything branches on the identifier, the catalogue is in data with a code appendix, and the next change will go in the appendix.

What does a catalogue entry need beyond an identifier and a name? Every parameter that varies between entries, plus effective dates and provenance. Without dates you cannot reproduce what the system did last month, which is exactly what you need when an old result is disputed. Without provenance the catalogue degrades silently, since a stale row still returns an answer.

What cannot be handled by adding a row? A new kind of thing rather than a new instance of a known kind. A new market is a row. A fundamentally different structure the engine does not model is an engine change. Pretending otherwise produces a configuration language that is secretly a programming language nobody can debug.

What is the risk of moving a catalogue into data? That a bad row now deploys instantly with no review. The catalogue needs validation on write, a review path for significant changes, and ideally a way to test an entry before it goes live, otherwise you have swapped a slow safe process for a fast dangerous one.

Carrying a program like this one?

Tell us the system, the stakes, and the date that matters. You get a straight technical reply from the person who would lead the work, within 24 hours.

Bring us the program