Quick Start

Get up and running with Sourcing in 5 minutes. We'll build a simple order management system with an aggregation (write side) and a projection (read side).

1. Install

zef install Sourcing

2. Define Events

Events are plain Raku classes that describe what happened:

use Sourcing;
use Sourcing::Plugin::Memory;

Sourcing::Plugin::Memory.use;

class OrderPlaced {
    has UInt $.order-id;
    has Str  $.customer;
    has Rat  $.total;
}

class OrderShipped {
    has UInt $.order-id;
    has Str  $.tracking;
}

3. Define an Aggregation

The aggregation validates commands and emits events:

aggregation Order {
    has UInt $.order-id is projection-id;
    has Str  $.status = 'pending';
    has Rat  $.total  = 0;

    # State changes come ONLY from events
    method apply(OrderPlaced $e) {
        $!status = 'placed';
        $!total = $e.total;
    }
    method apply(OrderShipped $e) {
        $!status = 'shipped';
    }

    # Command: validates then emits
    method place(Str $customer, Rat $total) is command {
        die "Total must be positive" if $total <= 0;
        $.order-placed: :$customer, :$total;
    }

    method ship(Str $tracking) is command {
        die "Order not placed" if $!status ne 'placed';
        $.order-shipped: :$tracking;
    }
}

4. Define a Projection

The projection builds a read-optimized view:

projection OrderSummary {
    has UInt $.order-id is projection-id;
    has Str  $.status = 'pending';
    has Rat  $.total  = 0;

    method apply(OrderPlaced $e) {
        $!status = 'placed';
        $!total = $e.total;
    }
    method apply(OrderShipped $e) {
        $!status = 'shipped';
    }
}

5. Use It

# Create an order
my $order = sourcing Order, :order-id(1);
$order.place: 'Alice', 99.95;
$order.^update;

say "Order status: {$order.status}";  # placed
say "Order total:  {$order.total}";   # 99.95

# Ship the order
$order.ship: 'TRACK-123';
$order.^update;

say "Order status: {$order.status}";  # shipped

# Query the projection
my $summary = sourcing OrderSummary, :order-id(1);
say "Summary: Order {$summary.order-id} is {$summary.status}";
# Summary: Order 1 is shipped

What Just Happened?

  1. sourcing Order, :order-id(1) — Created a fresh Order instance, replayed all events (none yet)
  2. $order.place: 'Alice', 99.95 — Validated total > 0, emitted OrderPlaced event
  3. $order.^update — Reset state, replayed all events from store, applied OrderPlaced
  4. sourcing OrderSummary, :order-id(1) — Created projection, replayed events, built summary view

Next Steps