Skip to main content

EventStore

The interface for reading and writing event streams in an event-sourced actor system.

What it does

EventStore is the persistence contract that EventSourcedBehavior (via the PersistenceEngine) uses to durably record domain events and replay them during actor recovery. It is intentionally minimal: four methods covering the full lifecycle of an event stream — append, load, delete, and sequence-number query.

Each event is wrapped in an EventEnvelope that carries the sequence number, writer ULID, and metadata alongside the raw event payload. The store guarantees append-only ordering: events are loaded in sequence-number order and the highest sequence number is used to resume replay without redundant scanning.

Nexus ships three implementations out of the box: InMemoryEventStore for unit tests, DbalEventStore for raw DBAL connections, and DoctrineEventStore for projects already using Doctrine ORM. All three honour the deleteUpTo() contract required by RetentionPolicy — making it safe to prune old events after a snapshot has been confirmed.

You only interact with EventStore directly when wiring a custom adapter or writing tests that inspect persisted events. In production actor code, the store is supplied once via EventSourcedBehavior::withEventStore() and never called directly.

Example

src/Persistence/InMemoryEventStore.php
use Monadial\Nexus\Persistence\Event\EventStore;
use Monadial\Nexus\Persistence\Event\EventEnvelope;
use Monadial\Nexus\Persistence\PersistenceId;

// Using the built-in in-memory store for tests:
$store = new InMemoryEventStore();

$behavior = EventSourcedBehavior::create(
PersistenceId::of('Order', $orderId),
new OrderState(),
$commandHandler,
$eventHandler,
)
->withEventStore($store)
->withSnapshotStore($snapshotStore)
->toBehavior();

// Or implement your own adapter:
final class RedisEventStore implements EventStore
{
public function persist(PersistenceId $id, EventEnvelope ...$events): void
{
foreach ($events as $envelope) {
// append to Redis stream keyed by $id
}
}

public function load(PersistenceId $id, int $fromSequenceNr = 0, int $toSequenceNr = PHP_INT_MAX): iterable
{
// yield EventEnvelope objects in ascending sequence order
}

public function deleteUpTo(PersistenceId $id, int $toSequenceNr): void
{
// remove events with sequenceNr <= $toSequenceNr
}

public function highestSequenceNr(PersistenceId $id): int
{
// return the highest known sequence number, or 0 if no events exist
return 0;
}
}

Key methods

  • persist(PersistenceId $id, EventEnvelope ...$events): void — append one or more event envelopes atomically; must preserve insertion order within the call.
  • load(PersistenceId $id, int $fromSequenceNr = 0, int $toSequenceNr = PHP_INT_MAX): iterable<EventEnvelope> — stream events in ascending sequence-number order; $fromSequenceNr is used to skip already-snapshotted history.
  • deleteUpTo(PersistenceId $id, int $toSequenceNr): void — permanently remove all events with sequenceNr <= $toSequenceNr; called by RetentionPolicy after a snapshot is confirmed.
  • highestSequenceNr(PersistenceId $id): int — return the current high-water mark; 0 means no events have been persisted yet.

Full API reference

Full method list and class hierarchy

See also

  • Persistence concept — event sourcing model, snapshot strategies, and retention policies
  • EventSourcedBehavior — the builder that accepts an EventStore instance via withEventStore()
  • PersistenceId — the stable identity used as the primary key in the store