Module 16 / Consistency across services

Module 16 · lesson 1 of 8 · Read 7 min

The problem

Payment succeeded, the order failed. Now what?

When Tiffin was one app with one database, placing an order was one transaction. Insert the order, record the payment, reserve the coupon, and COMMIT. If anything failed, the database rolled all of it back. You never had to think about "half an order".

Then Tiffin split into services (Module 11), and each service got its own database. Placing an order now writes to three or four databases owned by three or four teams. There's no COMMIT that covers all of them. This module is about what you do instead.

What checkout used to be

In the monolith, checkout looked like this:

BEGIN;
INSERT INTO orders (id, customer_id, restaurant_id, total, status)
  VALUES (5521, 9001, 4417, 640, 'placed');
INSERT INTO payments (order_id, amount, status, gateway_ref)
  VALUES (5521, 640, 'captured', 'pay_Q8x...');
UPDATE coupons SET used = true
  WHERE code = 'DIWALI50' AND customer_id = 9001 AND used = false;
-- if that updated 0 rows, the coupon was already used: ROLLBACK instead
COMMIT;

That BEGIN ... COMMIT gave you the ACID properties for free. The two that matter most here:

  • Atomicity. All three changes happen, or none do. No order without a payment record.
  • Isolation. While this transaction runs, no other transaction sees the half-done state. Nobody else can use DIWALI50 in between: the used = false check and the row lock make a second checkout wait, then find the coupon used.

(Even here there was a crack: the card charge itself happened at an outside payment gateway, which was never inside the transaction. Hold that thought.)