Skip to main content

Quickstart

This example uses the in-memory event store, so it is suitable for a test or a small executable example. The same aggregate and facade usage applies when the storage is replaced by a persistent adapter.

Define an event​

SourceEvent supplies the common aggregate ID, event ID, timestamp, and stream version fields. Event payload fields still need to be included by the concrete event's toArray() implementation.

use DomainFlow\EventSourcing\Aggregate\AggregateId;
use DomainFlow\EventSourcing\Event\SourceEvent;
use DomainFlow\EventSourcing\Interface\EntityIdentifierInterface;

final class ProductCreated extends SourceEvent
{
public function __construct(
public readonly string $name,
?EntityIdentifierInterface $aggregateId = null,
) {
parent::__construct($aggregateId, null);
}

public function toArray(): array
{
return [
...parent::toArray(),
'name' => $this->name,
];
}
}

An event starts with an unassigned stream version. The aggregate assigns the next version when the event is applied.

Define an aggregate​

An aggregate applies an event immediately and records it as uncommitted. During reconstitution, the same handlers are called without adding the replayed events to the uncommitted list.

use DomainFlow\EventSourcing\Aggregate\AggregateRoot;

final class Product extends AggregateRoot
{
private string $name = '';

public function __construct()
{
}

protected static function newInstance(): static
{
return new static();
}

public function create(AggregateId $id, string $name): void
{
$this->applyEvent(new ProductCreated($name, $id));
}

protected function applyProductCreated(ProductCreated $event): void
{
$this->name = $event->name;
}

public function name(): string
{
return $this->name;
}
}

The handler name is derived from the event's short class name: ProductCreated is handled by applyProductCreated().

Persist and reload​

use DomainFlow\EventSourcing\Facade\EventSourcingFacade;
use DomainFlow\EventSourcing\Storage\InMemoryEventStorage;

$facade = new EventSourcingFacade(new InMemoryEventStorage());
$productId = AggregateId::generate();

$product = new Product();
$product->create($productId, 'Keyboard');
$facade->persist($product);

$reloaded = $facade->load(Product::class, $productId);
assert($reloaded instanceof Product);
assert($reloaded->name() === 'Keyboard');

After a successful persist, the aggregate's uncommitted events are cleared. EventSourcingFacade::apply() is a convenience for the load/mutate/persist sequence:

$facade->apply(Product::class, $productId, static function (Product $product): void {
// Call a method that emits the next domain event.
});