Aggregations

An aggregation is the consistency boundary for business logic. It validates commands against current state and emits events that describe state changes. Aggregations are the command side (C) of CQRS.

What Aggregations Are

Aggregations are the gatekeepers of your domain. They answer: "Given what I know right now, is this command valid?" If yes, they emit an immutable event. If no, they reject the command.

Declaring an Aggregation

use Sourcing;

aggregation BankAccount {
    has Int $.account-id is projection-id;
    has Rat $.balance = 0;
    has Bool $.is-open = False;

    # State changes come ONLY from events
    method apply(AccountOpened $e) {
        $!balance = $e.initial-balance;
        $!is-open = True;
    }
    method apply(AmountDeposited $e) { $!balance += $e.amount }
    method apply(AmountWithdrawn $e) { $!balance -= $e.amount }
    method apply(AccountClosed $e)   { $!is-open = False }

    # Command: validates before emitting
    method withdraw(Rat $amount) is command {
        die "Account is closed" unless $!is-open;
        die "Amount must be positive" if $amount <= 0;
        die "Insufficient funds: balance is $!balance" if $!balance < $amount;

        $.amount-withdrawn: :$amount;
    }
}

Key Characteristics

PropertyDescription
Emits eventsAggregations are the only construct that can emit events. Events are immutable facts.
Enforces invariantsBusiness rules are enforced inside command methods. Invalid commands die before emitting.
One stream per instanceAll events for a given aggregate form a single append-only stream.
State is encapsulatedInternal state is private. External code uses command methods and public attributes.
Source of truthThe event stream is the authoritative record. State can always be rebuilt from it.

Command Methods

Methods marked with is command get special treatment:

  1. Reset + Replay — Before executing, ^update resets the aggregate and replays all events from the store
  2. Validation — The command body runs against fresh state
  3. Event emission — If validation passes, events are emitted with optimistic locking
  4. Automatic retry — If a concurrent modification is detected, the command retries (up to 5×)
method deposit(Rat $amount) is command {
    die "Account is closed" unless $!is-open;
    die "Amount must be positive" if $amount <= 0;

    $.amount-deposited: :$amount;  # Auto-generated emit method
}

Auto-Generated Emit Methods

For each event type handled by the aggregation, an emit method is auto-generated. The method name is the event class name converted to kebab-case:

Event ClassAuto-Generated Method
AmountDeposited$.amount-deposited(:$amount)
AccountOpened$.account-opened(:$initial-balance)
OrderPlaced$.order-placed(:$customer, :$total)

These methods automatically include the aggregation's projection ID values and handle optimistic locking.

Aggregate Design Rules

Common Mistake

Don't mutate $!attributes directly inside command methods. This bypasses the event stream and breaks the fundamental guarantee of event sourcing. Always emit events and let apply methods handle state changes.

Next Steps