← Back to Documentation

Variables and Data Flow

Variables in ARO hold values during execution. This chapter explains how variables are created, scoped, and how data flows through your application.

Variable Basics

Declaring Variables

Variables are implicitly declared when they appear as the result of an action:

<Extract> the <user-id> from the <request: parameters>.
(* user-id is now declared and bound *)

<Create> the <user> with <user-data>.
(* user is now declared and bound *)

There's no separate declaration step - variables come into existence when first assigned.

Variable Syntax

Variables use angle brackets:

<user>                (* Simple variable *)
<user-id>             (* Hyphenated name *)
<orderTotal>          (* Camel case - works but not recommended *)

Naming Conventions:

Qualified Variables

Add qualifiers to access properties:

<user: id>            (* The id property of user *)
<user: email>         (* The email property *)
<order: lineItems>    (* The lineItems property *)
<request: body>       (* The body of request *)

Qualifiers can be chained conceptually, but typically you extract step by step:

<Extract> the <order> from the <request: body>.
<Extract> the <items> from the <order: lineItems>.
<Extract> the <first-item> from the <items: first>.

Variable Scoping

Feature Set Scope

Variables are scoped to their feature set:

(GET /users/{id}: User API) {
    <Extract> the <user-id> from the <request: parameters>.
    (* user-id is available here *)

    <Retrieve> the <user> from the <repository> where id = <user-id>.
    (* user is available here *)

    <Return> an <OK: status> with <user>.
}
(* user-id and user are NOT available outside this feature set *)

No Global Variables

Variables don't persist across feature set executions:

(GET /count: Counter API) {
    (* This won't work - count doesn't persist *)
    <Set> the <count> to <count> + 1.
    <Return> an <OK: status> with <count>.
}

Use repositories for persistence:

(GET /count: Counter API) {
    <Retrieve> the <counter> from the <counter-repository>.
    <Compute> the <new-count> from <counter: value> + 1.
    <Store> the <counter> with { value: <new-count> } into the <counter-repository>.
    <Return> an <OK: status> with <new-count>.
}

Publishing Variables

Cross-Feature-Set Access

Use <Publish> to make variables available across feature sets:

(* In config.aro *)
(Load Config: 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 *)
(GET /settings: Settings API) {
    (* Access the published variable *)
    <Extract> the <port> from the <app-config: port>.
    <Return> an <OK: status> with <port>.
}

Publishing Guidelines

(* Good - publish configuration once *)
(Application-Start: My App) {
    <Read> the <config> from the <file: "./config.json">.
    <Publish> as <app-config> <config>.
    <Return> an <OK: status> for the <startup>.
}

(* Avoid - don't publish request data *)
(POST /users: User API) {
    <Create> the <user> with <data>.
    (* Don't do this - user is request-specific *)
    <Publish> as <current-user> <user>.
}

Data Flow Patterns

Linear Flow

Data flows through statements sequentially:

(Process Order: Order Management) {
    (* Input *)
    <Extract> the <order-data> from the <request: body>.

    (* Validation *)
    <Validate> the <order-data> for the <order-schema>.

    (* Transformation *)
    <Transform> the <order> from the <order-data>.

    (* Enrichment *)
    <Compute> the <total> for the <order: items>.
    <Transform> the <enriched-order> from the <order> with { total: <total> }.

    (* Persistence *)
    <Store> the <enriched-order> into the <order-repository>.

    (* Output *)
    <Return> a <Created: status> with <enriched-order>.
}

Branching Flow

Data can flow through different paths:

(GET /users/{id}: User API) {
    <Extract> the <user-id> from the <request: parameters>.
    <Retrieve> the <user> from the <repository> where id = <user-id>.

    if <user> is empty then {
        (* Error path *)
        <Return> a <NotFound: status> for the <missing: user>.
    }

    (* Success path *)
    <Transform> the <user-response> from the <user>.
    <Return> an <OK: status> with <user-response>.
}

Event-Driven Flow

Data flows between feature sets via events:

(* Source - emits event with data *)
(POST /orders: 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>.
}

(* Target - receives data from event *)
(Send Confirmation: OrderCreated Handler) {
    <Extract> the <order> from the <event: order>.
    <Extract> the <email> from the <order: customerEmail>.
    <Send> the <confirmation> to the <email>.
    <Return> an <OK: status> for the <notification>.
}

Type Hints

Adding Type Information

Provide type hints for clarity:

<Read> the <config: JSON> from the <file: "./config.json">.
<Read> the <image: bytes> from the <file: "./logo.png">.
<Transform> the <users: List> from the <response: data>.

Common Type Hints

Type Usage
JSON JSON data
bytes Binary data
List Array/collection
String Text data
Number Numeric data
Boolean True/false
Date Date/time

Special Variables

Request Variables

Available in HTTP handlers:

<request: method>      (* HTTP method: GET, POST, etc. *)
<request: path>        (* Request path *)
<request: parameters>  (* Path parameters *)
<request: query>       (* Query string parameters *)
<request: headers>     (* HTTP headers *)
<request: body>        (* Request body *)

Event Variables

Available in event handlers:

<event: type>          (* Event type name *)
<event: timestamp>     (* When event occurred *)
<event: ...>           (* Event-specific data *)

Shutdown Variables

Available in Application-End:

<shutdown: reason>     (* Why shutting down *)
<shutdown: code>       (* Exit code *)
<shutdown: signal>     (* Signal name if applicable *)
<shutdown: error>      (* Error if error shutdown *)

Immutability

Variables are Immutable

Once bound, a variable's value doesn't change:

<Extract> the <user-id> from the <request>.
(* user-id is now "123" - it won't change *)

(* To "modify", create a new variable *)
<Transform> the <new-value> from the <user-id>.

Creating Modified Copies

Use <Transform> with with to create modified copies:

<Retrieve> the <user> from the <repository> where id = <id>.
(* user is { name: "John", status: "pending" } *)

<Transform> the <active-user> from the <user> with { status: "active" }.
(* active-user is { name: "John", status: "active" } *)
(* user is still { name: "John", status: "pending" } *)

Repositories

Repositories are special variables that persist across HTTP requests and event handlers within the same business activity. They provide in-memory storage for application state.

Repository Naming

Repository names must end with -repository:

<message-repository>      (* Valid repository *)
<user-repository>         (* Valid repository *)
<order-repository>        (* Valid repository *)
<messages>                (* NOT a repository - regular variable *)

Storing Data

Use <Store> to save data to a repository:

(POST /messages: Chat API) {
    <Extract> the <message> from the <request: body>.
    <Store> the <message> into the <message-repository>.
    <Return> a <Created: status> with <message>.
}

Data is appended to the repository as a list.

Retrieving Data

Use <Retrieve> to fetch data from a repository:

(GET /messages: Chat API) {
    <Retrieve> the <messages> from the <message-repository>.
    <Return> an <OK: status> with <messages>.
}

Returns all items in the repository, or an empty list if empty.

Business Activity Scoping

Repositories are scoped to their business activity:

(* Both share "Chat API" - same repository *)
(postMessage: Chat API) {
    <Store> the <msg> into the <message-repository>.
}

(getMessages: Chat API) {
    <Retrieve> the <msgs> from the <message-repository>.
}

(* Different activity - different repository! *)
(logError: Logging API) {
    (* This is a separate message-repository *)
    <Store> the <error> into the <message-repository>.
}

For more details, see Repositories.

Best Practices

Descriptive Names

(* Good *)
<Extract> the <customer-email> from the <order: customerEmail>.
<Retrieve> the <pending-orders> from the <repository> where status = "pending".

(* Avoid *)
<Extract> the <e> from the <o: customerEmail>.
<Retrieve> the <x> from the <repository> where status = "pending".

Single Purpose

Each variable should have one purpose:

(* Good - clear purpose *)
<Retrieve> the <user> from the <repository>.
<Transform> the <user-dto> from the <user>.
<Return> an <OK: status> with <user-dto>.

(* Avoid - reusing variable name *)
<Retrieve> the <data> from the <repository>.
<Transform> the <data> from the <data>.  (* Confusing *)

Minimize Scope

Only create variables you need:

(* Good - minimal variables *)
(GET /users/{id}: User API) {
    <Extract> the <id> from the <request: parameters>.
    <Retrieve> the <user> from the <repository> where id = <id>.
    <Return> an <OK: status> with <user>.
}

(* Avoid - unnecessary intermediate variables *)
(GET /users/{id}: User API) {
    <Extract> the <params> from the <request: parameters>.
    <Extract> the <id> from the <params: id>.
    <Retrieve> the <result> from the <repository> where id = <id>.
    <Extract> the <user> from the <result>.
    <Return> an <OK: status> with <user>.
}

Next Steps