CO4 Week 7 of 12
Failure as a design concern: outcomes and idempotency
By Thursday every booking operation returns an explicit outcome, each failure is classified with a planned response, and submitting the same booking request twice creates exactly one booking.
Requirements: FR4FR7 (prepared)NFR4 (started)
Lecture topics
- Failure categories and Result-style control flow.
- Timeouts, unknown outcomes, and idempotency.
Skill focus
Classify failures and choose retry, return-error, or stop.
Lab manual programs
Lab 7: Exceptions and Safe Booking
Aim: Reject booking requests with invalid vehicle numbers or unavailable spots.
- Create a custom BookingException.
- Validate vehicle number, spot number, and spot availability in BookingService.
- Test valid and invalid booking requests and observe the messages.
Submit the Java source, the console output, and 3–5 lines explaining the concept.
Lab 10: Idempotency and Duplicate Booking Requests
Aim: Return the existing booking when the same request ID is submitted twice.
- Store bookings by request ID using a Map.
- Return the existing booking when a duplicate request ID is received.
- Submit a different request ID and explain why request IDs should be unique.
Submit the Java source, the console output, and 3–5 lines explaining the concept.
Thread: Monday's practical replaces ad‑hoc exceptions with Result and makes booking idempotent → Tuesday's skill block classifies 12 Smart Parking failures → Wednesday's lecture gives the theory: three species of failure, why a timeout is an unknown outcome, and four idempotency techniques → Thursday's skill block designs the retry policy that Week 8 implements.
Practical — P7: explicit outcomes and idempotent booking
Folded in from the lab manual: Lab 7 (custom BookingException; validate vehicle number, spot number and availability) as the starting point, and Lab 10 (store bookings by request ID; return the existing booking for a duplicate ID).
| Min | Step | In class | Your task |
|---|---|---|---|
| 0–10 | Build check | — | Build green. |
| 10–20 | Primer | Run Lab 7's BookingException version. Ask: "Is 'lot full' an error or a normal answer?" Then simulate a driver tapping "Book" twice on a slow network: two bookings appear. |
Note both problems. |
| 20–40 | Step 1: success and failure representation | Introduce Result and the sealed BookingFailure (below). Expected outcomes become Err; programmer errors stay unchecked exceptions. |
Convert BookingService.book(...) to return Result<Booking>. |
| 40–60 | Step 2: find duplicates | The client sends a RequestId (UUID) with every booking request. |
Write a failing test: same RequestId twice → two bookings. |
| 60–90 | Step 3: implement idempotency | Store Map<RequestId, Booking>; a repeat returns the stored booking; the same ID with a different vehicle is rejected (IdempotencyKeyReused). Thread safety comes in Week 9, so note it as a known gap. |
Implement; make the failing test pass; add tests for the reuse case and for all BookingFailure types. |
| 90–100 | Recap + submit | Q: "Why must the client, not the server, generate the request ID?" | Commit W7-P7. |
public sealed interface Result<T> permits Result.Ok, Result.Err {
record Ok<T>(T value) implements Result<T> {}
record Err<T>(BookingFailure failure) implements Result<T> {}
default <R> R fold(Function<T, R> onOk, Function<BookingFailure, R> onErr) {
if (this instanceof Ok<T> ok) return onOk.apply(ok.value());
return onErr.apply(((Err<T>) this).failure());
}
}
public sealed interface BookingFailure
permits NoSpotAvailable, InvalidVehicle, DuplicateActiveBooking,
IdempotencyKeyReused, PaymentDeclined, PaymentOutcomeUnknown {}
public record NoSpotAvailable(VehicleType type) implements BookingFailure {}
// ... one record per failure, each carrying only the data the caller needs
Acceptance checks: no catch (Exception e); lot‑full returns Err(NoSpotAvailable), not an exception; duplicate request returns the same BookingId; reused key with a different payload is rejected.
Deliverable (P): commit W7-P7, test output for duplicate and failure cases, 3–5 lines on why request IDs must be unique.
Skill A — S13: sealed failure taxonomy and Result control flow
| Min | Step | In class | Your task |
|---|---|---|---|
| 0–10 | Do‑now | "Should 'SMS provider down' fail a booking?" | Answer. |
| 10–20 | Primer | The skill plan's three responses: retry, return‑error, stop. | — |
| 20–40 | Step 1: classify | Rows 1–6 of the table below. | Fill species and response. |
| 40–60 | Step 2: classify | Rows 7–12. | Fill species and response. |
| 60–90 | Step 3: sealed hierarchy + flow | Map every "return‑error" row to a BookingFailure record; draw the control flow of book() with each branch (UML sequence diagram with alt fragments, diagram of the week). |
Update the sealed hierarchy in code; draw the flow. |
| 90–100 | Recap + present | Compare tables across two teams; disagreements are discussed, not marked wrong. | Present. |
Worksheet: fill in the blank cells yourself, then compare with your team.
| # | Event | Species | Response |
|---|---|---|---|
| 1 | No compatible spot free | ||
| 2 | Invalid registration number | ||
| 3 | Vehicle already has an active booking | ||
| 4 | Card declined | ||
| 5 | Payment gateway timeout | ||
| 6 | Gateway returns 503 | ||
| 7 | NullPointerException in allocator |
||
| 8 | checkOut on a COMPLETED booking |
||
| 9 | Database connection refused | ||
| 10 | SMS provider down | ||
| 11 | Disk full on the server | ||
| 12 | Sensor says spot occupied, system says free |
Deliverable (S13): docs/week07/failure-taxonomy.md (table + reasons), updated sealed hierarchy committed, sequence diagram with failure branches.
Lecture — L13 + L14: failure taxonomy and Result; timeouts, unknown outcomes, idempotency
| Min | Step | In class | Your task |
|---|---|---|---|
| 0–10 | Do‑now | "The payment call timed out after 5 s. Was the driver charged?" | Answer yes / no / can't tell. |
| 10–15 | Hook | Discuss in pairs. | — |
| 15–30 | Concept A: three species of failure | (1) Expected outcome (lot full, card declined): part of the domain, return it as a value. (2) Programmer error (null passed, illegal transition): fail fast with an unchecked exception, never retry. (3) Transient infrastructure (timeout, 503, connection refused): may succeed later, so retry with limits. Checked vs unchecked is a design decision: use checked only when the caller can and must recover. | Notes. |
| 30–50 | Apply A | Sort 8 Smart Parking events into the three species on the board; for each, choose return‑value, exception, or retry. | Sort and justify. |
| 50–55 | Bridge | "A timeout belongs to which species?" (None cleanly: it is an unknown outcome.) | — |
| 55–70 | Concept B: unknown outcomes and idempotency | A timeout has three possible states: charged, not charged, unknown. Keep the booking PAYMENT_PENDING; ask the gateway with the same key, or retry with the same key. Four common techniques to make an operation idempotent: (1) client‑supplied idempotency key with a stored result; (2) natural key or unique constraint (one active booking per vehicle); (3) conditional update ("check out only if ACTIVE", compare‑and‑set on a version); (4) absolute writes instead of increments ("set status = COMPLETED", not "count++"). | Notes. |
| 70–90 | Apply B | Design the idempotent pay‑at‑exit flow on the board: which key, who generates it, what is stored, what the second call returns. Choose a technique for each of book, checkOut, pay, cancel. |
Draw the flow. |
| 90–100 | Recap | Q1: Name the three species. Q2: Why is a timeout not a failure? Q3: Which technique fits checkOut, and why? |
Answer. |
Deliverable (L): the sorted failure list and the idempotent pay‑at‑exit flow with a technique chosen per operation.
Skill B — S14: retries with exponential backoff, jitter and budgets
| Min | Step | In class | Your task |
|---|---|---|---|
| 0–10 | Do‑now | "Each of 3 layers retries 3 times. One user tap → how many calls reach the gateway in the worst case?" | Compute. |
| 10–20 | Primer | Retries multiply; retry at one layer only. | — |
| 20–40 | Step 1: limits and backoff | Base 200 ms, multiplier 2, cap 3 s, 4 attempts. Delays: 200, 400, 800 ms. With a 2 s timeout per attempt the worst case is 4 × 2 + 1.4 = 9.4 s. Is that acceptable at a gate barrier? | Recompute for your own numbers. |
| 40–60 | Step 2: jitter and budgets | Full jitter: delay = random(0, min(cap, base × 2^attempt)), so 1,000 gates do not retry in the same instant after an outage. Retry budget: retries ≤ 10% of requests in a 10 s window; above that, stop retrying. When to stop: expected outcomes and programmer errors are never retried. | Write the stop rules. |
| 60–90 | Step 3: the policy | Require a one‑page policy for the payment call: what is retried, attempts, delays, timeout, budget, what happens when retries run out. | Write docs/week08/retry-policy.md and a retry timeline. |
| 90–100 | Recap + review | One team presents; others challenge one number. | Present. |
Deliverable (S14): retry policy document with worked numbers, used as the specification for Monday's Week 8 practical.