Skip to content

Base EventSourcing classes. #17

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions src/Domain/EventSourcing/EventSourcingAggregateRoot.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

namespace Ddd\Domain\EventSourcing;

use Ddd\Domain\DomainEvent;
use Ddd\Domain\DomainEventPublisher;

/**
* Class EventSourcingAggregateRoot
*/
trait EventSourcingAggregateRoot
{
/**
* @var DomainEvent[]
*/
private $recordedEvents;

/**
* @param DomainEvent $event
*/
protected function publishThat(DomainEvent $event)
{
$this->apply($event);
$this->record($event);
$this->notify($event);
}

/**
* @param DomainEvent $event
*/
protected function notify(DomainEvent $event)
{
DomainEventPublisher::instance()->publish($event);
}

/**
* @param DomainEvent $event
*/
protected function record(DomainEvent $event)
{
$this->recordedEvents[] = $event;
}

/**
* @param DomainEvent $event
*/
protected function apply(DomainEvent $event)
{
$parts = explode('\\', get_class($event));
$apply = 'apply' . end($parts);

$this->$apply($event);
}

/**
* @return DomainEvent[]
*/
public function recordedEvents()
{
return $this->recordedEvents;
}

/**
* @return void
*/
public function clearRecordedEvents()
{
$this->recordedEvents = [];
}
}
25 changes: 25 additions & 0 deletions src/Domain/EventSourcing/EventSourcingStore.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace Ddd\Domain\EventSourcing;

use Ddd\Domain\DomainEvent;

interface EventSourcingStore
{
/**
* @param string $aggregateId
* @param int $skipEvent
* @param int $maxCount
* @return EventStream
*/
public function loadEventStream($aggregateId, $skipEvent, $maxCount);


/**
* @param string $aggregateId
* @param DomainEvent[] $events
* @param int $expectedVersion
* @return void
*/
public function appendToStream($aggregateId, array $events, $expectedVersion);
}
49 changes: 49 additions & 0 deletions src/Domain/EventSourcing/EventStream.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

namespace Ddd\Domain\EventSourcing;

use Ddd\Domain\DomainEvent;
use IteratorAggregate;
use ArrayIterator;

/**
* Class EventStream
*/
class EventStream implements IteratorAggregate
{
/**
* @var int
*/
private $version;

/**
* @var DomainEvent[]
*/
private $events;

/**
* @param int $version
* @param DomainEvent[] $events
*/
public function __construct($version, array $events)
{
$this->version = $version;
$this->events = $events;
}

/**
* @return int
*/
public function version()
{
return $this->version;
}

/**
* @inheritdoc
*/
public function getIterator()
{
return new ArrayIterator($this->events);
}
}
35 changes: 0 additions & 35 deletions tests/Domain/DomainEventPublisherTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,38 +65,3 @@ private function unsubscribe($id)
DomainEventPublisher::instance()->unsubscribe($id);
}
}

class SpySubscriber implements DomainEventSubscriber
{
public $domainEvent;
public $isHandled = false;
private $eventName;

public function __construct($eventName)
{
$this->eventName = $eventName;
}

public function isSubscribedTo($aDomainEvent)
{
return $this->eventName === $aDomainEvent->name;
}

public function handle($aDomainEvent)
{
$this->domainEvent = $aDomainEvent;
$this->isHandled = true;
}
}

class FakeDomainEvent implements DomainEvent
{
public $name;

public function __construct($name)
{
$this->name = $name;
}

public function occurredOn() {}
}
100 changes: 100 additions & 0 deletions tests/Domain/EventSourcing/EventSourcingAggregateRootTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?php

namespace Ddd\Domain\EventSourcing;

use Ddd\Domain\DomainEventPublisher;
use Ddd\Domain\FakeDomainEvent;
use Ddd\Domain\SpySubscriber;

class EventSourcingAggregateRootTest extends \PHPUnit_Framework_TestCase
{
/**
* @var SpyEventSourcingAggregateRoot
*/
private $aggregateRoot;

/**
* @var SpySubscriber
*/
private $subscriber;

public function setUp()
{
$this->aggregateRoot = new SpyEventSourcingAggregateRoot();
$this->subscriber = new SpySubscriber('fake-event');

DomainEventPublisher::instance()->subscribe($this->subscriber);
}

/**
* @test
*/
public function itShouldPublishDomainEvent()
{
$this->aggregateRoot->someBusinessLogic();

$this->assertTrue($this->subscriber->isHandled);
}

/**
* @test
*/
public function itShouldApplyDomainEvent()
{
$this->aggregateRoot->someBusinessLogic();

$this->assertTrue($this->aggregateRoot->isApplied());
}

/**
* @test
*/
public function itShouldRecordDomainEvent()
{
$this->aggregateRoot->someBusinessLogic();

$this->assertEquals(
new FakeDomainEvent('fake-event'),
$this->aggregateRoot->recordedEvents()[0]
);
}

/**
* @test
*/
public function itShouldClearRecordedDomainEvent()
{
$this->aggregateRoot->someBusinessLogic();
$this->aggregateRoot->clearRecordedEvents();

$this->assertEmpty($this->aggregateRoot->recordedEvents());
}
}

class SpyEventSourcingAggregateRoot
{
use EventSourcingAggregateRoot;

/**
* @var bool
*/
private $isApplied = false;

public function someBusinessLogic()
{
$this->publishThat(new FakeDomainEvent('fake-event'));
}

protected function applyFakeDomainEvent(FakeDomainEvent $domainEvent)
{
$this->isApplied = true;
}

/**
* @return boolean
*/
public function isApplied()
{
return $this->isApplied;
}
}
15 changes: 15 additions & 0 deletions tests/Domain/FakeDomainEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Ddd\Domain;

class FakeDomainEvent implements DomainEvent
{
public $name;

public function __construct($name)
{
$this->name = $name;
}

public function occurredOn() {}
}
26 changes: 26 additions & 0 deletions tests/Domain/SpySubscriber.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Ddd\Domain;

class SpySubscriber implements DomainEventSubscriber
{
public $domainEvent;
public $isHandled = false;
private $eventName;

public function __construct($eventName)
{
$this->eventName = $eventName;
}

public function isSubscribedTo($aDomainEvent)
{
return $this->eventName === $aDomainEvent->name;
}

public function handle($aDomainEvent)
{
$this->domainEvent = $aDomainEvent;
$this->isHandled = true;
}
}