- Problem
- Closing a class touches six groups of data in one click (attendance, results, certificates, learning records, path progress, surveys), and the API had no Mongo transactions anywhere.
- Choice
- I designed closing as an idempotent saga: the plan is saved as a finalize-run document, a conditional findOneAndUpdate latch stops a second closer, an Idempotency-Key with a 3-minute lease covers retries, and certificates and notifications leave through an outbox.
- Why
- A transaction cannot take back a message already published to RabbitMQ or BullMQ, and rolling back 13 certificates because one learner failed is worse than finishing the rest and retrying the one.
- Trade-off
- The class has a visible 'closing' state the UI must show, and every step needs its own test.