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.