Key takeaways
- Isolate the path that takes money from everything else.
- Use events for work that doesn't need to finish before the customer gets a response.
- Each service should own its data; share through events, not shared tables.
- Distributed tracing is not optional once requests cross services.
When ordering, payments, menus and dispatch share one application, a slow component can take down checkout. How event-driven design isolates failures — and what it costs.
The problem with peaks
Food delivery demand arrives in sharp peaks around meal times. In a single application, menu browsing, recommendations, dispatch and checkout compete for the same threads, connections and memory. A slow query in one area can exhaust resources that checkout needs.
Protect the path that takes money
The first design principle was simple: the order and payment path must keep working even if everything else is degraded. That meant separating ordering and payments into their own services with their own data stores, and making every other interaction with them asynchronous.
Use events for everything that can wait
When an order is placed, the customer needs a confirmation immediately. Notifying the restaurant, assigning a rider and updating analytics can happen a moment later. Publishing an 'order placed' event to Kafka lets each of those consumers work at its own pace — and fail without affecting the order itself.
- Order service: accepts the order and publishes an event.
- Restaurant service: consumes the event and updates the kitchen view.
- Dispatch service: consumes the event and assigns a rider.
- Notification service: informs the customer as status events arrive.
Let each service own its data
Orders and payments need transactions, so they live in PostgreSQL. Menus are documents that change shape by restaurant, so they live in MongoDB. Services never read each other's tables; they keep the data they need up to date by consuming events.
What it costs
Event-driven systems are harder to reason about than a single application. Data is eventually consistent, failures can happen between services, and debugging requires following a request across several processes. Distributed tracing — Zipkin, in this case — and idempotent consumers are the minimum investment that makes the architecture manageable.
For a small internal tool, this would be over-engineering. For a platform whose revenue depends on checkout staying up at peak, it is the right trade-off.
Enjoyed this? Get the next one by email.

