Workspace/Mini projects
Loading progress
All mini projects
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

  1. Define legal business states and stable identifiers for forward and compensating operations.
  2. Run the supplied seed and annotate its successful and failed booking journals.
  3. Add a receipt ledger that detects an operation identifier reused with different arguments.
  4. Inject failures before and after each forward action and each compensation.
  5. Add persisted checkpoints in a learner-controlled local database and a restart command.
  6. 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.