Feature Sets
Feature sets are the primary organizational unit in ARO. They group related statements that work together to accomplish a business goal.
Defining Feature Sets
A feature set consists of a name, business activity, and body:
(Feature Name: Business Activity) {
(* statements *)
}
Naming Guidelines
Feature Set Name - Describes what the feature does:
- Use action-oriented names:
Create User,Calculate Total,Send Notification - Be specific:
Validate Email Formatnot justValidate - Use business terminology:
Process Refund,Approve Order
Business Activity - Describes the domain context:
- Use noun phrases:
User Management,Order Processing - Group related features: All user features might use
User API - Be consistent across your application
Feature Set Categories
Application Lifecycle
These special feature sets manage the application lifecycle:
(* Entry point - exactly one per application *)
(Application-Start: My Application) {
Log "Starting..." to the <console>.
Start the <http-server> on port 8080.
Return an <OK: status> for the <startup>.
}
(* Called on graceful shutdown - optional *)
(Application-End: Success) {
Stop the <http-server>.
Return an <OK: status> for the <shutdown>.
}
HTTP Route Handlers (Contract-First)
Feature sets named after OpenAPI operationId values handle HTTP requests:
(* Feature sets named after operationIds in openapi.yaml *)
(listUsers: User API) {
Retrieve the <users> from the <user-repository>.
Return an <OK: status> with <users>.
}
(createUser: User API) {
Extract the <user-data> from the <request: body>.
Create the <user> with <user-data>.
Store the <user> into the <user-repository>.
Return a <Created: status> with <user>.
}
(getUser: User API) {
Extract the <user-id> from the <pathParameters: id>.
Retrieve the <user> from the <user-repository> where id = <user-id>.
Return an <OK: status> with <user>.
}
Event Handlers
Feature sets with "Handler" in the business activity respond to events:
(* Handle domain events *)
(Send Welcome Email: UserCreated Handler) {
Extract the <user> from the <event: user>.
Extract the <email> from the <user: email>.
Send the <welcome-email> to the <email>.
Return an <OK: status> for the <notification>.
}
(* Handle file system events *)
(Process Upload: FileCreated Handler) {
Extract the <path> from the <event: path>.
Read the <content> from the <file: path>.
Transform the <processed> from the <content>.
Return an <OK: status> for the <processing>.
}
Feature Set Execution
Triggering
Feature sets are never called directly. They're triggered by:
- Application start:
Application-Startruns once at startup - HTTP requests: Route handlers match incoming requests via OpenAPI operationId
- Events: Event handlers respond to emitted events
- Application shutdown:
Application-Endruns during shutdown
Execution Flow
Within a feature set, statements are written and read in order, and every effect happens in that order:
(Process Order: Order Management) {
(* 1. Extract data *)
Extract the <order-data> from the <request: body>.
(* 2. Validate *)
Validate the <order-data> for the <order-schema>.
(* 3. Create order *)
Create the <order> with <order-data>.
(* 4. Store *)
Store the <order> into the <order-repository>.
(* 5. Emit event *)
Emit an <OrderCreated: event> with <order>.
(* 6. Return response *)
Return a <Created: status> with <order>.
}
What the runtime does not promise is that each statement finishes before the next one starts. An action begins where it is written, and the program waits for it at the first read of its result — so statements that need nothing from each other run at the same time. Two independent HTTP requests in one feature set take about as long as one.
Effects never move: Log, Store, Emit,
Send and Return run at their own statement and force
whatever they read first, so anything you can observe still happens in the
order you wrote it. See
ARO-0088
for the full rule.
Early Returns
Use control flow to return early:
(getUser: User API) {
Extract the <user-id> from the <pathParameters: id>.
Retrieve the <user> from the <user-repository> where id = <user-id>.
if <user> is empty then {
Return a <NotFound: status> for the <missing: user>.
}
Return an <OK: status> with <user>.
}
Organizing Feature Sets
By File
Organize related feature sets in files:
MyApp/
├── openapi.yaml # Required for HTTP server
├── main.aro # Application lifecycle
├── users.aro # User CRUD operations
├── orders.aro # Order management
├── payments.aro # Payment processing
├── notifications.aro # Email/SMS handlers
└── events.aro # Domain event handlers
Cross-File Communication
Publishing Variables
Make variables available to other feature sets:
(* In config.aro *)
(Load Configuration: Initialization) {
Read the <config: JSON> from the <file: "./config.json">.
Publish as <app-config> <config>.
Return an <OK: status> for the <loading>.
}
(* In any other file *)
(getSettings: Settings API) {
Extract the <timeout> from the <app-config: timeout>.
Return an <OK: status> with <timeout>.
}
Emitting Events
Trigger other feature sets via events:
(* In orders.aro *)
(createOrder: Order API) {
Create the <order> with <order-data>.
Store the <order> into the <order-repository>.
Emit an <OrderCreated: event> with <order>.
Return a <Created: status> with <order>.
}
(* In notifications.aro - automatically triggered *)
(Send Confirmation: OrderCreated Handler) {
Extract the <order> from the <event: order>.
Send the <confirmation-email> to the <order: customerEmail>.
Return an <OK: status> for the <notification>.
}
Best Practices
Single Responsibility
Each feature set should do one thing well:
(* Good - focused on one task *)
(Validate Email Format: Validation) {
Extract the <email> from the <input: email>.
Validate the <email> for the <email-pattern>.
Return an <OK: status> for the <validation>.
}
(* Avoid - too many responsibilities *)
(Handle User: User Management) {
(* Don't mix validation, creation, notification, and logging *)
}
Consistent Structure
Follow a consistent pattern:
(Feature Name: Domain) {
(* 1. Extract/validate inputs *)
Extract the <input> from the <source>.
Validate the <input> for the <schema>.
(* 2. Business logic *)
Create the <result> with <input>.
Transform the <output> from the <result>.
(* 3. Side effects *)
Store the <output> into the <repository>.
Emit an <Event: type> with <output>.
(* 4. Return *)
Return an <OK: status> with <output>.
}
Next Steps
Actions - Understanding and using actions
Application Lifecycle - Startup and shutdown