The numbers first
On the hotel side of our gateway, 75 supplier codes run on 31 modules. Across all five product lines it is roughly 150 codes on about 90 distinct integrations. The gap between those numbers is the subject of this lesson: the same integration, resold under many brand codes.
This is ordinary white-labelling. A wholesaler's inventory gets sold under a partner's brand: different credentials, different markup, sometimes a catalogue keyed on a different hotel-ID scheme — but the wire protocol, the request shapes, the quirks, all identical, because behind every one of those codes sits the same supplier system.
The tempting move
When the second brand arrives, the fastest thing in the world is to copy the module. Duplicate the folder, rename the class, change the credentials, ship. It works, it ships today, and the diff is trivially reviewable because it is all new files.
The cost arrives later and multiplies. Every future fix now lands N times — find the timeout bug in one copy, then remember, or fail to remember, the other copies. Worse than the multiplication is the drift: the copies disagree silently, because nothing forces them to disagree loudly. Copy three got the retry fix and copy five did not, and no compiler, no test, no diff will ever put those two files side by side and object. At 75 codes, a per-brand copy strategy would have meant one bug fixed up to 75 times. How many fixes that would have cost us in practice I cannot count — we never ran it that way, which is the point.
What runs instead
One module per supplier system, written against the fixed contract every module implements, and registered in the single registry once per code — the alias is a registration entry, not a fork. Seventy-five names, thirty-one implementations.
The differences between brands live in data. A sparse settings table holds, per supplier code, only the keys that differ from the defaults — credentials, endpoints, the handful of behavioural switches a brand genuinely needs. "Sparse" is the load-bearing word: a row does not restate the defaults, so reading a row shows you exactly and only what makes this brand special. One more map records which hotel-ID column each supplier code's catalogue keys on, because the same building carries a different ID at every supplier and a brand may resell the catalogue under yet another scheme.
Brand behaviour became data. Adding brand number seventy-six is a registry entry and a settings row, not a code review.
The flip side, stated honestly
This choice has a failure mode, and it is a mean one: a wrong settings row is now a production bug that no code diff will ever show. The module is correct. The tests pass, because the tests exercise the module. The MR that broke the brand does not exist, because nothing was merged — someone wrote a row, and the row is wrong.
The only defence is to treat the data with the discipline diffs used to give you for free: after writing a settings row, read it back and check what the resolved configuration actually says, defaults merged in, before calling the brand live. Our standard tool for reaching this database is a small query script rather than the stock mysql client, and running the read-back through it costs one SELECT. The habit is the fix; nothing structural forces it.
The rule
When variants of the same thing multiply, put the variance in data and keep the mechanism single — one module, many codes, differences as rows. But say out loud what you traded: you have moved the bug surface out of code diffs, where reviews and tests live, into rows, where nothing lives unless you build the habit of reading them back. The multiplication you avoided was real. So is the new place bugs hide.