Repositories
Repositories provide persistent in-memory storage that survives across HTTP requests and event handlers. Unlike regular variables which are scoped to a single feature set execution, repositories maintain state for the lifetime of the application.
Overview
In ARO, each HTTP request creates a fresh execution context. Variables defined in one request aren't available in another:
(* This won't work - count resets on each request *)
(GET /count: Counter API) {
<Create> the <count> with 0.
<Compute> the <new-count> from <count> + 1.
<Return> an <OK: status> with <new-count>.
}
Repositories solve this by providing shared storage:
(POST /increment: Counter API) {
<Retrieve> the <counts> from the <counter-repository>.
<Compute> the <current> from <counts: length>.
<Store> the <current> into the <counter-repository>.
<Return> an <OK: status> with <current>.
}
(GET /count: Counter API) {
<Retrieve> the <counts> from the <counter-repository>.
<Compute> the <total> from <counts: length>.
<Return> an <OK: status> with { count: <total> }.
}
Repository Naming Convention
Repository names must end with -repository. This is how ARO distinguishes repositories from regular variables:
(* These are repositories *)
<user-repository>
<message-repository>
<order-repository>
<session-repository>
(* These are NOT repositories - just regular variables *)
<users>
<messages>
<user-data>
The naming convention:
- Makes repositories visually distinct in code
- Enables automatic persistence by the runtime
- Follows ARO's self-documenting code philosophy
Storing Data
Use the <Store> action to save data to a repository:
<Store> the <data> into the <name-repository>.
Preposition Variants
All of these are equivalent:
<Store> the <user> into the <user-repository>.
<Store> the <user> in the <user-repository>.
<Store> the <user> to the <user-repository>.
Storage Semantics
Repositories use list-based storage. Each store operation appends to the list:
(* First request *)
<Store> the <user1> into the <user-repository>.
(* Repository: [user1] *)
(* Second request *)
<Store> the <user2> into the <user-repository>.
(* Repository: [user1, user2] *)
(* Third request *)
<Store> the <user3> into the <user-repository>.
(* Repository: [user1, user2, user3] *)
Example: Storing Messages
(postMessage: Chat API) {
<Extract> the <data> from the <request: body>.
<Extract> the <text> from the <data: message>.
<Extract> the <author> from the <data: author>.
<Create> the <message> with {
text: <text>,
author: <author>,
timestamp: now
}.
<Store> the <message> into the <message-repository>.
<Return> a <Created: status> with <message>.
}
Retrieving Data
Use the <Retrieve> action to fetch data from a repository:
<Retrieve> the <items> from the <name-repository>.
Return Value
- Returns a list of all stored items
- Returns an empty list
[]if the repository is empty or doesn't exist - Never throws an error for missing repositories
Example: Retrieving All Messages
(getMessages: Chat API) {
<Retrieve> the <messages> from the <message-repository>.
<Return> an <OK: status> with { messages: <messages> }.
}
Filtered Retrieval
Use where to filter results:
(getUserById: User API) {
<Extract> the <id> from the <pathParameters: id>.
<Retrieve> the <user> from the <user-repository> where id = <id>.
<Return> an <OK: status> with <user>.
}
Single Item Retrieval
Use specifiers to retrieve a single item from a repository:
(* Get the most recently stored item *)
<Retrieve> the <message> from the <message-repository: last>.
(* Get the first stored item *)
<Retrieve> the <message> from the <message-repository: first>.
(* Get by numeric index (0-based) *)
<Retrieve> the <message> from the <message-repository: 0>.
This is useful when you only need one item, like the latest message in a chat:
(getLatestMessage: Chat API) {
<Retrieve> the <message> from the <message-repository: last>.
<Return> an <OK: status> with { message: <message> }.
}
If the repository is empty, an empty string is returned.
Business Activity Scoping
Repositories are scoped to their business activity. Feature sets with the same business activity share repositories:
(* Same business activity: "Chat API" *)
(* These share the same <message-repository> *)
(postMessage: Chat API) {
<Store> the <message> into the <message-repository>.
<Return> a <Created: status>.
}
(getMessages: Chat API) {
<Retrieve> the <messages> from the <message-repository>.
<Return> an <OK: status> with <messages>.
}
(deleteMessage: Chat API) {
(* Same repository as above *)
<Retrieve> the <messages> from the <message-repository>.
(* ... *)
}
Different Business Activities = Different Repositories
(* Business activity: "Chat API" *)
(postMessage: Chat API) {
<Store> the <msg> into the <message-repository>.
}
(* Business activity: "Admin API" - DIFFERENT repository! *)
(postAuditLog: Admin API) {
(* This <message-repository> is separate from Chat API's *)
<Store> the <log> into the <message-repository>.
}
This scoping:
- Prevents accidental data leakage between domains
- Allows reuse of generic repository names
- Enforces domain boundaries
Complete Example: Simple Chat Application
main.aro
(Application-Start: Simple Chat) {
<Log> the <startup: message> for the <console> with "Starting Simple Chat...".
<Start> the <http-server> for the <contract>.
<Keepalive> the <application> for the <events>.
<Return> an <OK: status> for the <startup>.
}
api.aro
(* GET /status - Return the last message *)
(getStatus: Simple Chat API) {
<Retrieve> the <message> from the <message-repository: last>.
<Return> an <OK: status> with { message: <message> }.
}
(* POST /status - Store a new message *)
(postStatus: Simple Chat API) {
<Extract> the <message> from the <body: message>.
<Store> the <message> into the <message-repository>.
<Return> a <Created: status> with { message: <message> }.
}
Testing
# Post a message
curl -X POST http://localhost:8080/status \
-H 'Content-Type: application/json' \
-d '{"message":"Hello!"}'
# Response: {"message":"Hello!"}
# Post another message
curl -X POST http://localhost:8080/status \
-H 'Content-Type: application/json' \
-d '{"message":"World!"}'
# Response: {"message":"World!"}
# Get the last message
curl http://localhost:8080/status
# Response: {"message":"World!"}
Lifetime and Persistence
Application Lifetime
Repositories persist for the lifetime of the application:
- Created when first accessed
- Survive across all HTTP requests
- Cleared when application restarts
No Disk Persistence
Repositories are in-memory only:
- Data is lost when the application stops
- No external database required
- Fast and simple for prototyping
For persistent storage, use a database integration (future ARO feature).
Best Practices
Use Descriptive Repository Names
(* Good - clear what's stored *)
<user-repository>
<pending-order-repository>
<session-token-repository>
(* Avoid - too generic *)
<data-repository>
<stuff-repository>
One Repository Per Domain Concept
(* Good - separate repositories for different concepts *)
<user-repository>
<order-repository>
<product-repository>
(* Avoid - mixing concepts *)
<everything-repository>
Keep Repository Data Simple
Store simple, serializable data:
(* Good - simple object *)
<Create> the <user> with {
id: <id>,
name: <name>,
email: <email>
}.
<Store> the <user> into the <user-repository>.
(* Avoid - complex nested structures *)
<Store> the <entire-request-context> into the <debug-repository>.