Aggregates

An aggregate is the current state derived from replaying all events for a given type and ID.

How aggregates work

When you query an aggregate, j17 replays all events and applies each handler in order:

Event 1: was_created { name: "Alice" }        -> State: { name: "Alice", created_at: 1705312800 }
Event 2: had_email_updated { email: "..." }    -> State: { name: "Alice", email: "...", created_at: ... }
Event 3: had_name_updated { name: "Alicia" }   -> State: { name: "Alicia", email: "...", created_at: ... }

Each event type has a handler defined in your spec that determines how it transforms state. For example:

{
  "was_created": {
    "handler": [
      { "set": { "target": "", "value": "$.data" } },
      { "set": { "target": "created_at", "value": "$.metadata.timestamp" } }
    ]
  },
  "had_name_updated": {
    "handler": [
      { "merge": { "target": "", "value": "$.data" } }
    ]
  }
}

Querying aggregates

curl https://myapp.j17.dev/user/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer $J17_API_KEY"

Response:

{
  "ok": true,
  "state": {
    "name": "Alicia",
    "email": "alice@example.com",
    "created_at": 1705312800
  },
  "length": 3
}

The length field indicates how many events have been applied. You can use this for optimistic concurrency control.

Aggregate IDs

Aggregate IDs in URLs accept three formats:

Format Example Use case
UUIDv4 550e8400-e29b-41d4-a716-446655440000 Default for all entities
Humane code ABC123XYZ Human-readable codes (promos, bookings)
global global Singleton aggregates (config, rate limits)

Use UUIDs by default. Humane codes and singletons exist for specific edge cases.

Can't find what you need? support@j17.app