From the outside, a booking platform looks like a catalogue with a payment step bolted on. Search, list, pick, pay. I have written elsewhere about the mechanics of running ninety-odd supplier integrations at once; this is about what sits underneath them. The domain does not behave like a catalogue, and every architectural decision worth defending comes from that.
What you are selling is not in your database
A hotel night is not a row you own. It is a claim about a room in a building owned by someone else, sold through a supplier who is selling the same room to other people at the same moment. You do not hold the inventory. You hold an opinion about the inventory, formed at the moment you asked, and it starts decaying immediately.
So the interval between search and book is not a UI transition, it is a window in which the world changes. The room goes. The rate is withdrawn. The same search, run twice a minute apart, is allowed to return different things and neither answer is a bug. Every catalogue system has a stale-cache problem; here staleness has money attached, and the answer is not "cache less" but "assume the answer expires and design the checkout around that". The 30-second deadline over our hotel, flight and car search is one visible consequence: a supplier answering at second 31 is answering about a world that has moved on.
A price is a claim with a shelf life
This is why a prebook step exists at all. The price in a search result is an advertisement; the price at prebook is what the supplier will honour right now, for a short while. Between the two the rate can change, vanish, or come back with a different tax component. The flight side adds an explicit price and tax confirmation before a PNR is generated, for exactly the same reason, one product line further along.
The design consequence is small to state and expensive to hold: nothing may treat a price as a plain value. It is a value plus a source plus a moment, and a component that keeps only the first has quietly become a place where money gets lost.
A cancellation policy is data, not a flag
"Free cancellation" is a filter checkbox and a lie in a schema. The real object is per rate: one or more windows, each with a deadline and an amount, sometimes expressed as a night, sometimes as a percentage, sometimes with a no-show clause that behaves differently from a cancellation. Two rates on the same room, the same night, from the same supplier, routinely differ.
That structure is load-bearing. Our test bookings always pick the cheapest offer with free cancellation, which is only possible because the policy is structured enough to be selected on. Flatten it to a boolean anywhere in the chain and someone downstream reconstructs it wrongly — and you find out from a customer charged for a cancellation your screen told them was free.
Five products that refuse to be one product
We run five lines, and they are not five skins over one object. A hotel night is a date range plus occupancy against a room. A flight is a set of legs with a PNR, fare rules and an e-ticket after payment. A tour has a departure date and a capacity, priced per person. A transfer has two endpoints and a pickup time, often tied to a flight arrival it does not control. A car hire is a rental period with a pickup and a drop-off that may be in different places.
You can put one contract over all of them — we do — but the contract is an agreement about which questions get asked, not a claim that the answers are the same kind of object. Flight is the giveaway: it needs steps no other line has, because a ticket is a different thing from a reservation. A model that erases those differences re-introduces them as special cases in the places that were supposed to be generic.
Your supply is other companies
Ninety integrations is ninety independent failure domains. Each has its own maintenance window, its own opinion of what an error is, its own weekend. You cannot fix their bug, you cannot deploy their fix, and one of ninety is always wrong. Your effective availability is yours multiplied by theirs; I have never computed that product per supplier, so no figure from me — but the direction is not in question.
Correctness is federated too, not just uptime. The same building carries a different identifier at every supplier, and identity is transitive, which is why lookups run as a graph traversal while the rows live in a relational table of about 1.9 million identities with roughly 46 supplier-ID columns. There is no authority to ask. A name and a coordinate are a hint; another supplier's code appearing on both sides is evidence. Building a business on facts you cannot verify from a single source is a domain property, not an engineering choice.
A wrong answer is a financial event
In most systems the worst outcome of a defect is a bad render. Here the worst outcome is a booking. A mapping error puts a customer in a different building. A price that comes out low is not a display bug, it is a decision to sell at a loss, executed automatically until somebody notices. A duplicate booking is two charges and a refund conversation. A cancellation that did not reach the supplier is a bill.
That is why the operational habits look paranoid from outside: every provider call logged with request and response and re-fireable as the exact curl it sent, writes to production data gated behind an explicit confirmation that prints where it is about to run, certification evidence crawled into a folder per testcase. None of it is diligence theatre. In this domain the undo button is a person on a phone.
Why the domain forces architecture
Add them up and you get requirements nobody chose for elegance: state that survives a deploy mid-checkout, timeouts that are product decisions rather than tuning, isolation so one supplier's bad afternoon is not everyone's, policies as structured data, an identity layer that is a graph because reality is.
Build the obvious thing — a catalogue, a cart, a checkout — and the domain does not correct you politely. It agrees for a demo, then bills you. The difficulty here is not hidden in the details. It is the subject.