Designing Reliable Booking and Duty State Transitions
A practical field guide to explicit workflow state, permissions, downstream billing effects, auditability, and frontend–backend consistency.
- Workflow design
- State machines
- Fleet operations
Full-Stack Developer specializing in Spring Boot
Operational software often starts with a small status field. A booking might be NEW, CONFIRMED, or CANCELLED. A duty might be ASSIGNED, STARTED, or COMPLETED. The first release can treat these values as labels. The difficult work begins when each value starts controlling permissions, financial records, driver actions, notifications, and reports.
This guide presents a safe, generic approach based on recurring mobility and operations problems. The names and examples are illustrative; the design principles apply to any workflow in which state controls authority, money, or downstream work.
A status is rarely just a status. It becomes a compressed representation of several facts:
- what has already happened;
- what may happen next;
- who may perform the next action;
- which records must exist before the action;
- whether a financial calculation is provisional or final;
- what the user interface should offer;
- what operators need when something fails.
Complexity grows when those facts are encoded in different places. A controller checks one condition, a service checks another, the frontend hides a button using a third interpretation, and a scheduled job assumes a fourth. Each local check may look reasonable. Together they create an undocumented state machine.
The risk is not only invalid data. The larger risk is operational ambiguity: two users see different possible actions, an invoice reflects an outdated duty state, or a retry repeats a transition that was already applied.
A state machine does not require a dedicated framework. It requires an explicit model of states, events, guards, and effects.
Scattered conditionals answer a narrow question: “Can this line of code run?” An explicit transition model answers a system question: “Can this event move this record from its current state to the requested state under this authority and business context?”
public TransitionResult apply(
Duty duty,
DutyEvent event,
AuthorityContext authority
) {
TransitionRule rule = rules.find(duty.status(), event)
.orElseThrow(() -> new InvalidTransition(duty.status(), event));
rule.authorize(authority, duty.organisationId());
rule.validate(duty);
return rule.transition(duty);
}The example is intentionally generic. The important boundary is that lookup, authorization, validation, and state change are visible parts of one operation.
Request to controlled transition
A high-level path for processing a workflow event.
Receive event
Identify the resource, requested event, actor, and request context.
Evaluate guards
Check current state, authority, required records, and business invariants.
Commit effects
Persist the new state, audit record, and controlled downstream work.
Text alternative
A workflow request enters the application. The application checks transition availability, actor authority, and required business data. When every guard passes, it records the new state and audit information, then schedules or invokes approved downstream effects.
The transition catalogue should be readable by engineering, product, and operations. A table is often more useful than a diagram during review because it forces each event and guard to be stated.
| Workflow | Current state | Event | Next state | Example guard |
|---|---|---|---|---|
| Booking | Requested | Confirm | Confirmed | Service details and authority are valid |
| Booking | Confirmed | Allocate | Allocated | An eligible duty or resource is available |
| Booking | Confirmed | Cancel | Cancelled | Cancellation policy permits the action |
| Duty | Assigned | Start | In progress | Actor is authorized and prerequisites exist |
| Duty | In progress | Complete | Completed | Completion data passes validation |
| Duty | Completed | Close | Closed | Required review and billing inputs are present |
The catalogue is not the whole implementation. It is the contract around which implementation can be tested. It also exposes missing questions early: Can an allocated booking return to confirmed? Can a completed duty be corrected? Does cancellation create a compensating financial action?
Transition permission should be checked at the backend boundary even when the interface already hides unavailable actions. UI visibility improves usability; it is not authorization.
Useful permission inputs may include:
- actor role and explicit authorities;
- organisation or tenant ownership;
- relationship to the booking, duty, or driver;
- current workflow state;
- whether a privileged override has been approved;
- whether the action would alter a protected financial record.
Keep role checks separate from transition rules where possible. “Operations manager” may describe a person. “May close a reviewed duty for this organisation” describes the authority needed by the operation. The second form is easier to test and evolve.
Operational state often becomes financial input. A duty completion may expose distance, time, toll, waiting, adjustment, or tax context. Closing a workflow too early can freeze incomplete information; reopening it without controls can make an issued invoice inconsistent.
The transition design should state which effects are synchronous and which are deferred:
- State and audit data usually belong in the primary transaction.
- A calculation that defines the validity of the transition may also belong in that transaction.
- Notifications, document generation, and external synchronization may be queued after commit.
- Issued financial records may require a correction workflow rather than silent recalculation.
Avoid hiding these effects inside generic status setters. A method named setStatus(COMPLETED) communicates less than an operation named completeDuty(command) with explicit prerequisites and outcomes.
The current value answers “where is the workflow now?” An audit trail answers “how did it get here?” Both are required when operators, finance users, and engineers need to reconstruct a problem.
A useful transition record can capture:
- workflow identifier and organisation boundary;
- previous and next state;
- event name;
- actor or system identity;
- timestamp;
- correlation identifier;
- reason code for overrides or cancellation;
- safe reference to related downstream work.
Do not use the audit record as a dumping ground for request bodies or sensitive personal data. Store the minimum evidence needed to understand the transition.
The backend owns validity. The frontend needs enough state and capability information to present the workflow honestly.
One approach is to return allowed actions alongside the current resource representation:
{
"id": "duty-example",
"status": "IN_PROGRESS",
"allowedActions": ["COMPLETE", "REPORT_EXCEPTION"],
"version": 7
}The interface can use allowedActions to render controls, while the backend re-evaluates every guard when an action is submitted. The version can support optimistic concurrency so an action based on stale state fails clearly instead of overwriting newer work.
Consistency also depends on language. If the interface says “Close duty” while the API event is FINALIZE_SERVICE and operations documentation says “Complete trip,” teams will eventually reason about different workflows. Agree on terms, even when display labels are friendlier than internal event names.
Workflow failures are expected behavior, not exceptional engineering surprises. Design responses for at least these categories. For a deeper treatment of public error contracts, correlation IDs, and protected diagnostics, read From Business Error to Production Diagnosis.
| Failure | Safe response | Diagnostic need |
|---|---|---|
| Invalid transition | Explain that the action is no longer available | Current state, requested event, correlation ID |
| Missing authority | Return a generic forbidden response | Actor authority and organisation scope in protected logs |
| Stale version | Ask the user to refresh the workflow | Expected and actual version |
| Missing prerequisite | Identify the business field or review step | Validation rule and resource reference |
| Downstream provider failure | Preserve committed state or compensate explicitly | Provider category, attempt, correlation ID |
| Duplicate request | Return the prior result when idempotent | Idempotency key and original operation reference |
The final design question is not “How many statuses exist?” It is “Can every important state change be explained, authorized, retried safely, and connected to its operational and financial consequences?”
Write the transition catalogue first, keep authority and business guards explicit, commit audit evidence with the state change, and treat downstream work as part of the design. That is the threshold between a status field and a reliable workflow model.