fivenines
9/38

Guided Problem

YourRide Build 07: Guard the Trip Lifecycle

Time
20m
Level
foundation
Artifacts
not specified
Progress0%
Lesson 1.7 99.9%

Trip lifecycle: the state machine of record

Same pattern as driver onboarding (1.2): long-running process = persisted state machine. A trip is shorter (minutes not days) but higher stakes: money (1.8) and safety hang off its transitions. Matching (1.6) hands us a trip in Assigned.

The obvious design is to store the latest trip status and trust each client to send transitions in a sensible order. It makes the happy path simple, but it protects the wrong property. Only an authorized command valid for the current state may advance a trip; refused commands leave both current state and historical evidence intact.

The Trip service is the system of record for what actually happened. Every transition is an immutable event row (who, when, where) appended alongside the current-state column — cancellation fees, fare calculation, support disputes, and regulatory reports (1.13) all replay this record. States and guards:

stateDiagram-v2
    [*] --> Requested : rider confirms quote
    Requested --> Assigned : matcher assigns driver (1.6)
    Requested --> NoDriver : candidates exhausted
    Assigned --> Arriving : driver en route to pickup
    Arriving --> Waiting : driver within 50 m of pickup
    Waiting --> InProgress : driver taps start — GPS at pickup
    InProgress --> Completed : driver taps end — fare finalized
    Assigned --> CancelledByRider : may incur fee after grace period
    Arriving --> CancelledByRider : fee if driver near
    Waiting --> CancelledByDriver : no-show after 5 min wait
    Assigned --> CancelledByDriver : penalized — re-dispatch rider
    Completed --> [*]
    NoDriver --> [*]
    CancelledByRider --> [*]
    CancelledByDriver --> [*]
The trip state machine. Guards (GPS proximity, wait timers, grace periods) prevent state transitions from being taken on faith.

Two design rules keep this robust. First, transitions are commands with guards, not notifications: "start trip" is rejected unless the trip is in Waiting and the driver's GPS is near the pickup — the state machine defends itself against buggy or malicious clients. Second, during the trip, the location stream (1.4) is forked: pings from a driver on an active trip are additionally appended to that trip's route trail, which becomes the billing-grade distance record at completion. Quotes estimated with cell math; fares settle on the recorded route.

sequenceDiagram
    autonumber
    participant D as Driver app
    participant T as Trip service
    participant L as Location service
    participant R as Rider app
    participant P as Payments (1.8)
    D->>T: start trip
    T->>T: guard — state Waiting, GPS at pickup? OK → InProgress
    T->>R: trip started
    loop every 4 s during trip
        D->>L: ping
        L->>T: append to trip route trail
        T-->>R: live position + updated ETA
    end
    D->>T: end trip
    T->>T: guard OK → Completed. Fare = rates × recorded route
    T->>P: capture fare {trip id, amount}
    T-->>R: trip complete — receipt pending
    T-->>D: earnings updated
Worked example: the in-progress trip. The same pings that animate the rider's map become the immutable billing record.

GPS lies: tunnels, urban canyons, phones dying mid-trip. The trail recorder must tolerate gaps (interpolate for billing, flag large gaps for review) and the state machine must allow ops to repair a trip stuck in InProgress because a phone died at the destination. "What does support do when this wedges?" is a design requirement, not an afterthought.

Next step

See what actually stuck.

Take the practice scenarios now.