MODULE 21 · 6 HOUR BUILD
A booking recovery journal
Build a reproducible simulator for reservations, payment capture, ticketing, and compensation. Produce an event journal and a crash-point recovery matrix that explain every externally visible effect.
Build evidence Record your actual checks, results, and limitations.
Build it in stages
- Define legal business states and stable identifiers for forward and compensating operations.
- Run the supplied seed and annotate its successful and failed booking journals.
- Add a receipt ledger that detects an operation identifier reused with different arguments.
- Inject failures before and after each forward action and each compensation.
- Add persisted checkpoints in a learner-controlled local database and a restart command.
- Write a report distinguishing confirmed failure, unknown outcome, compensation pending, and completed recovery.
Your acceptance criteria
Use these as your project review. Record commands, outputs, and failure cases in your repository.
- All documented crash points have a deterministic recovery test.
- Repeated delivery of the same booking produces no additional net charge or room reservation.
- Conflicting reuse of an operation identifier is rejected.
- Failed compensation remains visible and cannot be reported as fully recovered.
A working starting point
The seed runs as supplied. Extend it to satisfy the full brief. It is a teaching starting point, not a finished portfolio submission.
main.py
python
from dataclasses import dataclass, field
@dataclass
class BookingService:
rooms: int = 3
balance: int = 0
results: dict = field(default_factory=dict)
journal: list = field(default_factory=list)
def book(self, booking_id, ticket_available):
if booking_id in self.results:
return self.results[booking_id]
completed = []
try:
if self.rooms == 0:
raise ValueError("no room")
self.rooms -= 1
completed.append("room")
self.journal.append((booking_id, "room reserved"))
self.balance += 120
completed.append("payment")
self.journal.append((booking_id, "payment captured"))
if not ticket_available:
raise ValueError("no ticket")
self.journal.append((booking_id, "ticket issued"))
status = "confirmed"
except ValueError as error:
self.journal.append((booking_id, str(error)))
for action in reversed(completed):
if action == "payment":
self.balance -= 120
self.journal.append((booking_id, "payment refunded"))
else:
self.rooms += 1
self.journal.append((booking_id, "room released"))
status = "compensated"
self.results[booking_id] = status
return status
def main():
service = BookingService()
print("A:", service.book("A", False))
print("B:", service.book("B", True))
print("B again:", service.book("B", True))
print("rooms:", service.rooms, "balance:", service.balance)
for booking_id, event in service.journal:
print(booking_id, event)
if __name__ == "__main__":
main()
Push it further
Add two concurrent bookings and a semantic lock or version check, then demonstrate a stale compensation being rejected.