# Events ARO is fundamentally event-driven. Feature sets respond to events rather than being called directly. This chapter explains how events work and how to build event-driven applications. ## Event-Driven Architecture In ARO, feature sets are **triggered by events**, not called directly: ``` ┌───────────────────────────────────────────────────────────────────┐ │ Event Bus │ ├───────────────────────────────────────────────────────────────────┤ │ │ │ HTTPRequest ──────► (listUsers: User API) [via operationId] │ │ │ │ FileCreated ──────► (Process: FileCreated Handler) │ │ │ │ ClientConnected ──► (Handle: ClientConnected Handler) │ │ │ │ RepositoryChanged ► (Audit: user-repository Observer) │ │ │ └───────────────────────────────────────────────────────────────────┘ ``` ## Event Types ### HTTP Events (Contract-First) ARO uses **contract-first** HTTP development. Routes are defined in `openapi.yaml`, and feature sets are named after `operationId` values: **openapi.yaml:** ```yaml openapi: 3.0.3 info: title: User API version: 1.0.0 paths: /users: get: operationId: listUsers post: operationId: createUser /users/{id}: get: operationId: getUser ``` **handlers.aro:** ```aro (* Triggered by GET /users - matches operationId *) (listUsers: User API) { the from the . an with . } (* Triggered by POST /users *) (createUser: User API) { the from the . the with . a with . } (* Triggered by GET /users/123 *) (getUser: User API) { the from the . the from the where id = . an with . } ``` ### File System Events Triggered by file system changes: ```aro (* File created *) (Process New File: FileCreated Handler) { the from the . the from the . the from the . an for the . } (* File modified *) (Reload Config: FileModified Handler) { the from the . the from the when is "./config.json". as when is "./config.json". an for the . } (* File deleted *) (Log Deletion: FileDeleted Handler) { the from the . "File deleted: ${path}" to the . an for the . } ``` ### Socket Events Triggered by TCP connections: ```aro (* Client connected *) (Handle Connection: ClientConnected Handler) { the from the . the
from the . "Client connected: ${address}" to the . an for the . } (* Data received *) (Process Data: DataReceived Handler) { the from the . the from the . the from the . the to the . an for the . } (* Client disconnected *) (Handle Disconnect: ClientDisconnected Handler) { the from the . "Client disconnected: ${client-id}" to the . an for the . } ``` ### Repository Observers Repository observers automatically react to changes in repositories. They enable reactive programming patterns where code responds to data mutations without explicit coupling. **Naming Pattern:** ```aro (Feature Name: {repository-name} Observer) ``` The `{repository-name}` must match a repository name (ending in `-repository`). **Trigger Conditions:** | Change Type | When Triggered | |-------------|----------------| | `created` | New item stored via `` action | | `updated` | Existing item replaced via `` action (matching ID) | | `deleted` | Item removed via `` action | **Event Payload Fields:** | Field | Type | Description | |-------|------|-------------| | `event: repositoryName` | String | Repository name (e.g., "user-repository") | | `event: changeType` | String | "created", "updated", or "deleted" | | `event: entityId` | String? | ID of entity (if has "id" field) | | `event: newValue` | Any? | New value (nil for deletes) | | `event: oldValue` | Any? | Previous value (nil for creates) | | `event: timestamp` | Date | When the change occurred | **Example: Audit Logging** ```aro (Audit Changes: user-repository Observer) { the from the . the from the . the from the . the from "[AUDIT] " + + ": " + + " (id: " + + ")". to the . an for the . } ``` **Example: Change Tracking** ```aro (Track Updates: user-repository Observer) { the from the . the from the . the from the . the from "User changed: " + . to the . (* Compare old and new values for updates *) an for the . } ``` Multiple observers can respond to the same repository—they all execute independently and concurrently. ## Handling Events ### Handler Naming Event handlers include "Handler" in the business activity: ```aro (Feature Name: EventName Handler) ``` Examples: ```aro (Index Content: FileCreated Handler) { ... } (Reload Config: FileModified Handler) { ... } (Echo Data: DataReceived Handler) { ... } (Log Connection: ClientConnected Handler) { ... } ``` ### Accessing Event Data Use `` to get event data: ```aro (Process Upload: FileCreated Handler) { the from the . the from the . the from the . the from the . the into the . an for the . } ``` ### Multiple Handlers Multiple handlers can respond to the same event: ```aro (* Handler 1: Log the file *) (Log Upload: FileCreated Handler) { the from the . "File uploaded: ${path}" to the . an for the . } (* Handler 2: Index the file *) (Index Upload: FileCreated Handler) { the from the . the from the . the into the . an for the . } (* Handler 3: Notify admin *) (Notify Upload: FileCreated Handler) { the from the . the to the . an for the . } ``` All handlers execute independently when the event is emitted. ## Built-in Events ### Application Events | Event | When Triggered | |-------|----------------| | `ApplicationStarted` | After Application-Start completes | | `ApplicationStopping` | Before Application-End runs | ### File Events | Event | When Triggered | |-------|----------------| | `FileCreated` | File created in watched directory | | `FileModified` | File modified in watched directory | | `FileDeleted` | File deleted in watched directory | | `FileRenamed` | File renamed in watched directory | ### Socket Events | Event | When Triggered | |-------|----------------| | `ClientConnected` | TCP client connects | | `DataReceived` | Data received from client | | `ClientDisconnected` | TCP client disconnects | ### Repository Events | Event | When Triggered | |-------|----------------| | `RepositoryChanged` | Item created, updated, or deleted in repository | ## State Transition Events State transition events are emitted automatically when the `` action successfully transitions a state field. These events enable reactive programming around state changes. ### StateObserver Pattern Feature sets become state observers when their business activity matches the pattern: ```aro (Feature Name: fieldName StateObserver) (* All transitions on field *) (Feature Name: fieldName StateObserver) (* Specific transition only *) ``` The `fieldName` filters which field's transitions to observe. The optional `` filter restricts to a specific transition. ### Example: Audit Logging (All Transitions) ```aro (* Observe all status changes *) (Audit Order Status: status StateObserver) { the from the . the from the . the from the . the from "[AUDIT] Order ${orderId}: ${fromState} -> ${toState}". to the . an for the . } ``` ### Example: Shipping Notification (Specific Transition) ```aro (* Notify ONLY when order ships (paid -> shipped) *) (Send Shipping Notice: status StateObserver) { the from the . the from the . the from the . the to the with { subject: "Your order has shipped!", body: "Track your package: ${tracking}" }. an for the . } ``` ### Transition Data Fields | Field | Type | Description | |-------|------|-------------| | `transition: fieldName` | String | The field that changed (e.g., "status") | | `transition: objectName` | String | The object type (e.g., "order") | | `transition: fromState` | String | Previous state value | | `transition: toState` | String | New state value | | `transition: entityId` | String? | ID from object's "id" field, if present | | `transition: entity` | Object | Full object after transition | ### Multiple Observers Multiple observers can react to the same transition: ```aro (* Observer 1: Audit all transitions *) (Log Transitions: status StateObserver) { "State changed" to the . an for the . } (* Observer 2: Only on draft -> placed *) (Notify Placed: status StateObserver) { the to the . an for the . } (* Observer 3: Only on shipped -> delivered *) (Track Delivery: status StateObserver) { the by 1. an for the . } ``` All matching observers execute independently when a transition occurs. ## Custom Domain Events Beyond built-in events, you can define and emit your own domain events: ### Emitting Events Use the `` action to publish custom events: ```aro (createUser: User API) { the from the . the with . the in the . (* Emit a domain event *) a with . a with . } ``` ### Handling Custom Events Handle custom events using the `Handler` pattern: ```aro (* Send welcome email when user is created *) (Send Welcome: UserCreated Handler) { the from the . the from the . the to the with . an for the . } (* Update analytics when user is created *) (Track Signup: UserCreated Handler) { the from the . the to the with . an for the . } ``` ### Event Chains Handlers can emit additional events, creating processing chains: ```aro (* OrderPlaced triggers inventory reservation *) (Reserve Stock: OrderPlaced Handler) { the from the . the for the . an with . an for the . } (* InventoryReserved triggers payment processing *) (Process Payment: InventoryReserved Handler) { the from the . the to the with . a with . an for the . } ``` ### Circular Event Chain Detection The ARO compiler detects circular event chains at compile time. If handlers form a cycle where events trigger each other indefinitely, the compiler reports an error: ``` error: Circular event chain detected: OrderPlaced → InventoryReserved → OrderPlaced hint: Event handlers form an infinite loop that will exhaust resources hint: Consider breaking the chain by using different event types or adding termination conditions ``` **Example of a circular chain (will not compile):** ```aro (* BAD: Creates infinite loop *) (Handle Alpha: EventAlpha Handler) { the for the . an for the . } (Handle Beta: EventBeta Handler) { the for the . (* Triggers Alpha again! *) an for the . } ``` **Breaking cycles:** - Use different event types that don't loop back - Design linear workflows where each step moves forward - Move repeated logic into a single handler ## Long-Running Applications For applications that need to stay alive to process events (servers, file watchers, etc.), use the `` action: ```aro (Application-Start: File Watcher) { "Starting file watcher..." to the . (* Start watching a directory *) the as . (* Keep the application running to process file events *) the for the . an for the . } ``` The `` action: - Blocks execution until a shutdown signal is received (SIGINT/SIGTERM) - Allows the event loop to process incoming events - Enables graceful shutdown with Ctrl+C ## Best Practices ### Keep Handlers Focused ```aro (* Good - single responsibility *) (Log File Upload: FileCreated Handler) { the from the . "Uploaded: ${path}" to the . an for the . } (* Avoid - too many responsibilities *) (Handle File: FileCreated Handler) { (* Don't do logging, indexing, notifications, and analytics in one handler *) } ``` ### Handle Events Idempotently Events may be delivered multiple times: ```aro (Process File: FileCreated Handler) { the from the . (* Check if already processed *) the from the where path = . (* Already processed - skip *) an for the when is not empty. (* Process file *) the from the . the from the . the into the . an for the . } ``` ## Next Steps - [Application Lifecycle](Guide-Application-Lifecycle) - Startup and shutdown events - [HTTP Services](Guide-HTTP-Services) - Contract-first HTTP routing - [File System](Guide-File-System) - File system events