← Back to Documentation

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:

Business Activity - Describes the domain context:

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:

  1. Application start: Application-Start runs once at startup
  2. HTTP requests: Route handlers match incoming requests via OpenAPI operationId
  3. Events: Event handlers respond to emitted events
  4. Application shutdown: Application-End runs 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