Problem
A hospitality SaaS product priced every room identically regardless of occupancy, season, or local events. Competitors offering dynamic pricing were winning deals. The product needed to think.
Context
The existing system was a ten-year-old monolith. Pricing happened inside a stored procedure that three people were afraid to touch. There were 1,400 active contracts all referencing the old pricing model. Any migration that broke a single invoice was a support crisis.
Constraint
Zero downtime. No contract renegotiation. The business could not tell clients that pricing was changing — it had to simply improve. And it had to ship in six weeks.
The decision
What we chose and why.
Instead of replacing the pricing model, I introduced an event-sourced pricing layer alongside it. The monolith continued to emit pricing requests; a new engine answered them. The engine's decisions were idempotent and fully auditable. The old system gradually received less traffic until it was inert.
Tradeoffs
Auditability was a hard requirement. When a pricing decision is wrong, we need to replay exactly what the engine saw.
The cost of a mistake was too high. Gradual rollout let us catch race conditions under 2% traffic before they affected 100%.
Rules encode yesterday's market. A signal lets the engine's decision function evolve without redeployment.
Architecture
Click any node to inspect
The failure
A race condition in concurrent booking scenarios caused price drift. If two bookings were priced within 50ms of each other and occupancy crossed a threshold between them, the second booking used stale occupancy data.
DISCOVERED — Discovered during load testing at 3× normal booking volume. Caught before production.
IMPACT — Estimated drift of ~3% on affected bookings. In testing only — zero customer impact.
Iteration
Added optimistic locking with version vectors on the occupancy store. Each pricing call now includes the occupancy snapshot version it read from. If another call has advanced the version, the second call retries. Latency increased by 8ms at the 99th percentile — a fair trade.
Outcome
$2.4M in automated pricing decisions. Zero customer complaints.
The product team gained a competitive differentiator that took a year to develop but six weeks to deploy safely. The pricing engine now handles seasonal peaks without human intervention. Three competitors are still using static pricing.
Lessons
When correctness matters more than speed, make state transitions explicit and auditable.
Gradual migration is always slower and always worth it.
A race condition under 2× load is a production incident waiting to happen. Test at 5×.
Event sourcing adds complexity upfront. It removes all the complexity later.