Skip to main content

Events and aggregates

Domain events

DomainEventInterface requires an aggregate identifier, occurrence timestamp, stream version, array representation, and event metadata. It also allows the infrastructure to assign a version and to create a copy with different metadata.

For common events, extend SourceEvent. Its constructor accepts:

__construct(
?EntityIdentifierInterface $aggregateId,
?EntityIdentifierInterface $eventId,
?DateTimeImmutable $occurredOn = null,
?EventVersion $version = null,
)

Passing null generates an ID or timestamp. SourceEvent stores occurrence times in UTC. If an event has additional payload fields, override toArray() and merge the parent array, as shown in the quickstart.

Applying and replaying

AggregateRoot::applyEvent($event) does two things for a new event:

  1. it invokes the matching apply<EventShortName>() method, if one exists;
  2. it assigns the next stream version and adds the event to the uncommitted list.

AggregateRoot::reconstitute() creates an empty aggregate through the concrete class's newInstance() method and applies stored events in stream order. During replay, the stored event version is authoritative and the event is not marked as uncommitted.

An aggregate therefore contains current state plus the events that still need to be stored:

$aggregate->getUncommittedEvents();
$aggregate->clearUncommittedEvents();
$aggregate->getAggregateVersion();

The facade and repository manage clearing uncommitted events after a successful storage call. Domain code normally only calls domain methods that emit events.

Stream versions

Stream versions are one-based. EventVersion::unassigned() is version 0 and is used before an event has been applied to an aggregate. A newly applied event gets version 1, the next gets version 2, and so on.

Do not confuse an event's stream version with its payload schema version. The stream version identifies its position for one aggregate; the optional _schemaVersion payload field identifies the shape of the event data.

Aggregate identifiers

An aggregate ID only needs to implement EntityIdentifierInterface. The package provides AggregateId, backed by a DomainFlow UUIDv6, and the generic EntityIdentifier implementation. Custom identifier value objects can be used as long as they implement the interface and can be reconstructed from a string.