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.
});