Event Store

Where do we persist Domain Events? In an Event Store. An Event Store is a Domain Event Repository that lives in our Domain space as an abstraction (interface or abstract class). Its responsibility is to append Domain Events and query them. A possible basic interface could be the following:

interface EventStore
{
public function append(DomainEvent $aDomainEvent);
public function allStoredEventsSince($anEventId);
}

However, depending on the usage of your Domain Events, the previous interface can have more methods to query your Events.

In terms of implementation, you can decide to use a Doctrine Repository, a DBAL one, or a plain PDO. Because Domain Events are immutable, using a Doctrine Repository adds an unnecessary performance penalty, though for a small to medium application, Doctrine is probably OK. Let's look at a possible implementation with Doctrine:

class DoctrineEventStore extends EntityRepository implements EventStore
{
private $serializer;

public function append(DomainEvent $aDomainEvent)
{
$storedEvent = new StoredEvent(
get_class($aDomainEvent),
$aDomainEvent->occurredOn(),
$this->serializer()->serialize($aDomainEvent, 'json')
);

$this->getEntityManager()->persist($storedEvent);
}

public function allStoredEventsSince($anEventId)
{
$query = $this->createQueryBuilder('e');
if ($anEventId) {
$query->where('e.eventId > :eventId');
$query->setParameters(['eventId' => $anEventId]);
}
$query->orderBy('e.eventId');

return $query->getQuery()->getResult();
}

private function serializer()
{
if (null === $this->serializer) {
/** JMSSerializerSerializerSerializerBuilder */
$this->serializer = SerializerBuilder::create()->build();
}

return $this->serializer;
}
}

StoredEvent is the Doctrine Entity needed to map to the database. As you may have seen, when appending and after persisting the Store, there's no flush call. If this operation is inside a Doctrine transaction, it's not needed. So, let's leave it without the call and we'll go into more details when talking about Application Services.

Now let's see the StoredEvent implementation:

class StoredEvent implements DomainEvent
{
private $eventId;
private $eventBody;
private $occurredOn;
private $typeName;

/**
* @param string $aTypeName
* @param DateTimeImmutable $anOccurredOn
* @param string $anEventBody
*/
public function __construct(
$aTypeName, DateTimeImmutable $anOccurredOn, $anEventBody
) {
$this->eventBody = $anEventBody;
$this->typeName = $aTypeName;
$this->occurredOn = $anOccurredOn;
}

public function eventBody()
{
return $this->eventBody;
}

public function eventId()
{
return $this->eventId;
}

public function typeName()
{
return $this->typeName;
}

public function occurredOn()
{
return $this->occurredOn;
}
}

And here is its mapping:

DddDomainEventStoredEvent:
type: entity
table: event
repositoryClass:
DddInfrastructureApplicationNotificationDoctrineEventStore
id:
eventId:
type: integer
column: event_id
generator:
strategy: AUTO
fields:
eventBody:
column: event_body
type: text
typeName:
column: type_name
type: string
length: 255
occurredOn:
column: occurred_on
type: datetime

In order to persist Domain Events with different fields, we'll have to join those fields as a serialized string. typeName identifies the Domain-wide Domain Event. An Entity or Value Object makes sense inside a Bounded Context, but Domain Events define a communication protocol between Bounded Contexts.

In distributed systems, shit happens. You'll have to deal with Domain Events that aren't published, are lost somewhere in the chain, or are published more than once. That's why it's important to persist a Domain Event with an ID, so that it's easy to track which Domain Events have been published and which are missing.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset