Metadata and personal data
Event metadata
Metadata is stored beside the event payload. EventMetadata supports optional
correlation, causation, actor, tenant, and custom values:
$metadata = EventMetadata::empty()
->withCorrelationId($correlationId)
->withActorId($actorId)
->withCustom(['requestId' => $requestId]);
$eventWithMetadata = $event->withMetadata($metadata);
AmbientMetadataContext is a mutable, instance-based context for a request,
message, or command. Call begin() at the start of a unit of work and end()
after it. MetadataEnricher, passed to the facade, fills missing metadata when
events are about to be stored; explicit event metadata wins over ambient
defaults.
EventDispatcher can use the same context as a causation tracker. While a
subscriber handles an event, events it causes can receive that event's ID as
their causation ID.
Crypto-shredding
EncryptingEventEntryFactory decorates an existing entry factory. Mark the
property carrying the data and the property identifying the data subject:
use DomainFlow\EventSourcing\Attribute\DataSubjectId;
use DomainFlow\EventSourcing\Attribute\PersonalData;
final class CustomerRegistered extends SourceEvent
{
public function __construct(
#[DataSubjectId]
public readonly string $subjectId,
#[PersonalData]
public readonly string $email,
?EntityIdentifierInterface $aggregateId = null,
) {
parent::__construct($aggregateId, null);
}
}
Personal-data properties must hold strings. If toArray() stores a property
under another key, provide it as #[PersonalData(key: 'email_address')].
Values are encrypted with a per-subject key. Erasure destroys that key, leaves
the append-only event unchanged, and returns RedactedValue::MARKER when the
event is read afterwards.
Use PersonalDataEraser to forget a subject and delete snapshots for the
aggregates known by the consumer to contain that subject's data:
$eraser->erase($subjectId, $aggregateId);
The key store must be durable in production. InMemoryPersonalDataKeyStore is
only a reference implementation and test double. Deleting the key alone does
not remove decrypted data from snapshots; configure snapshot storage/history so
the eraser can delete those snapshots too.