State Transitions
ARO handles state machines elegantly: define states as OpenAPI enums,
transition with the Accept action, and let the runtime validate everything.
States Are Just Enums
No special state machine syntax. No transition tables. Define your states in OpenAPI,
use Accept to transition, and get automatic validation with clear error messages.
State Machine Example
Consider an order lifecycle. Orders flow through these states:
Defining States in OpenAPI
States are defined as string enums in your openapi.yaml:
# openapi.yaml
openapi: 3.0.3
info:
title: Order Management
version: 1.0.0
components:
schemas:
OrderStatus:
type: string
enum:
- draft
- placed
- paid
- shipped
- delivered
- cancelled
Order:
type: object
properties:
id:
type: string
status:
$ref: '#/components/schemas/OrderStatus'
customerId:
type: string
required:
- id
- status
The Accept Action
Use Accept to validate and apply state transitions.
The syntax clearly shows the expected transition:
(* Transition from draft to placed *)
Accept the <transition: draft_to_placed> on <order: status>.
(* Transition from placed to paid *)
Accept the <transition: placed_to_paid> on <order: status>.
(* Transition from paid to shipped *)
Accept the <transition: paid_to_shipped> on <order: status>.
The syntax uses _to_ as the separator because to is a reserved preposition in ARO.
Complete Example
Here's how state transitions work in practice with order management:
(placeOrder: Order Management) {
Extract the <order-id> from the <pathParameters: id>.
Retrieve the <order> from the <order-repository>.
(* Accept state transition from draft to placed *)
Accept the <transition: draft_to_placed> on <order: status>.
Store the <order> into the <order-repository>.
Emit to <Send Order Confirmation> with <order>.
Return an <OK: status> with <order>.
}
(payOrder: Order Management) {
Extract the <order-id> from the <pathParameters: id>.
Retrieve the <order> from the <order-repository>.
(* Must be placed to accept payment *)
Accept the <transition: placed_to_paid> on <order: status>.
Store the <order> into the <order-repository>.
Return an <OK: status> with <order>.
}
(shipOrder: Order Management) {
Extract the <order-id> from the <pathParameters: id>.
Retrieve the <order> from the <order-repository>.
(* Must be paid to ship *)
Accept the <transition: paid_to_shipped> on <order: status>.
Store the <order> into the <order-repository>.
Emit to <Send Shipping Notification> with <order>.
Return an <OK: status> with <order>.
}
Error Handling
If the current state doesn't match the expected state, the runtime throws a clear error:
This follows ARO's "Code Is The Error Message" philosophy. The error tells you exactly what was expected and what was found.
Why This Approach?
Simplicity
No new keywords. No special constructs. Just OpenAPI enums + Accept.
Use standard control flow (if, match) for complex logic.
Clarity
The transition is explicit in the code. Reading
Accept the <transition: draft_to_placed> on <order: status>
tells you exactly what's happening.
Safety
The runtime validates:
- Current state matches expected
fromstate - Target state is a valid enum value
- Field exists on the object
State Observers
State observers react to transitions after they occur. Use them for audit logging, notifications, analytics, and side effects.
Observer Pattern
A feature set becomes a state observer when its business activity ends with StateObserver.
Add an optional transition filter in angle brackets to observe only specific transitions:
(* Observe ALL status field transitions *)
(Audit Status: status StateObserver) {
Extract the <fromState> from the <transition: fromState>.
Extract the <toState> from the <transition: toState>.
Extract the <orderId> from the <transition: entityId>.
Log the <audit> for the <console>
with "Order ${orderId}: ${fromState} -> ${toState}".
Return an <OK: status> for the <audit>.
}
(* Observe ONLY draft->placed transition *)
(Notify Placed: status StateObserver<draft_to_placed>) {
Extract the <orderId> from the <transition: entityId>.
Log the <notification> for the <console>
with "Order ${orderId} has been placed!".
Return an <OK: status> for the <notification>.
}
Transition Data
Observers access transition information via the <transition> object:
transition: fieldName- The field that changed (e.g., "status")transition: objectName- The object type (e.g., "order")transition: fromState- Previous state valuetransition: toState- New state valuetransition: entityId- Entity ID if availabletransition: entity- Full object after transition
Use Cases
- Audit logging - Record all state changes for compliance
- Notifications - Alert customers when orders ship
- Analytics - Track state transition metrics
- Side effects - Trigger downstream processes
Observer Semantics
Observers execute after the transition succeeds. Multiple observers
can react to the same transition. Observers run in isolation - failures
don't affect the Accept action or other observers.
Learn More
See the full state objects specification in ARO-0013: State Objects.