Aggregates
An order may hold at most 100 items. To enforce that, the command needs the order’s current total, and it needs it to be exact: a read model that lags one event behind could let the 101st item through. An aggregate replays the order’s own events into memory, checks the rule, and applies the new event. Because it knows which revision it replayed, a concurrent change rejects the append instead of breaking the rule.
The API is experimental and part of the optional Chronicle integration. Ordinary Arc commands do not need an event store.
Aggregate or read model
Section titled “Aggregate or read model”| Aggregate | Read model in a command | |
|---|---|---|
| State comes from | Replaying the event source’s events on every command | A projection Chronicle stored earlier |
| Freshness | Every stored event of the handled types | Whatever the projection has processed |
| Concurrency | The append is rejected when the stream moved after the replay | None; the read model does not lock anything |
| Cost | Grows with the number of events in the stream | One lookup |
| Use it when | A rule depends on exact history of one event source | The command needs context, or a rule can tolerate lag |
A command can take both. See Read models in commands.
How Arc wires it
Section titled “How Arc wires it”- You define a class that extends
AggregateRootand registers a handler per event type. - A command binds it with
@inject(commandAggregate(Order)). - Before
handle()runs, Arc loads the events for the command’s key, replays them through the handlers, and records the tail it read. handle()calls methods on the aggregate, whichapply()new events.- When the command succeeds, the applied events join the command’s batch, with the recorded tail as the expected revision.
Topics
Section titled “Topics”| Topic | Description |
|---|---|
| Defining an aggregate root | Handlers, state, rules, and what the TypeScript aggregate does not have |
| Injecting into commands | Binding, identity and routing, commit, concurrency, and boundaries |