Skip to content
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

Extract aggregate root into traits to make it easier to avoid domain extending infrastructure #62

Merged
merged 5 commits into from
May 24, 2017
Merged
Show file tree
Hide file tree
Changes from 3 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
63 changes: 63 additions & 0 deletions src/Aggregate/EventProducerTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php
/**
* This file is part of the prooph/event-sourcing.
* (c) 2014-2017 prooph software GmbH <contact@prooph.de>
* (c) 2015-2017 Sascha-Oliver Prolic <saschaprolic@googlemail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Prooph\EventSourcing\Aggregate;

use Prooph\EventSourcing\AggregateChanged;

trait EventProducerTrait
{
/**
* Current version
*
* @var int
*/
protected $version = 0;

/**
* List of events that are not committed to the EventStore
*
* @var AggregateChanged[]
*/
protected $recordedEvents = [];

/**
* Get pending events and reset stack
*
* @return AggregateChanged[]
*/
protected function popRecordedEvents(): array
{
$pendingEvents = $this->recordedEvents;

$this->recordedEvents = [];

return $pendingEvents;
}

/**
* Record an aggregate changed event
*/
protected function recordThat(AggregateChanged $event): void
{
$this->version += 1;

$this->recordedEvents[] = $event->withVersion($this->version);

$this->apply($event);
}

/**
* Apply given event
*/
abstract protected function apply(AggregateChanged $event): void;
}
58 changes: 58 additions & 0 deletions src/Aggregate/EventSourcedTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php
/**
* This file is part of the prooph/event-sourcing.
* (c) 2014-2017 prooph software GmbH <contact@prooph.de>
* (c) 2015-2017 Sascha-Oliver Prolic <saschaprolic@googlemail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Prooph\EventSourcing\Aggregate;

use Iterator;
use Prooph\EventSourcing\AggregateChanged;
use RuntimeException;

trait EventSourcedTrait
{
/**
* Current version
*
* @var int
*/
protected $version = 0;

/**
* @throws RuntimeException
*/
protected static function reconstituteFromHistory(Iterator $historyEvents): self
{
$instance = new static();
$instance->replay($historyEvents);

return $instance;
}

/**
* Replay past events
*
* @throws RuntimeException
*/
protected function replay(Iterator $historyEvents): void
{
foreach ($historyEvents as $pastEvent) {
/** @var AggregateChanged $pastEvent */
$this->version = $pastEvent->version();

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

/**
* Apply given event
*/
abstract protected function apply(AggregateChanged $event): void;
}
76 changes: 4 additions & 72 deletions src/AggregateRoot.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,35 +12,13 @@

namespace Prooph\EventSourcing;

use Iterator;
use RuntimeException;
use Prooph\EventSourcing\Aggregate\EventProducerTrait;
use Prooph\EventSourcing\Aggregate\EventSourcedTrait;

abstract class AggregateRoot
{
/**
* Current version
*
* @var int
*/
protected $version = 0;

/**
* List of events that are not committed to the EventStore
*
* @var AggregateChanged[]
*/
protected $recordedEvents = [];

/**
* @throws RuntimeException
*/
protected static function reconstituteFromHistory(Iterator $historyEvents): self
{
$instance = new static();
$instance->replay($historyEvents);

return $instance;
}
use EventProducerTrait;
use EventSourcedTrait;

/**
* We do not allow public access to __construct, this way we make sure that an aggregate root can only
Expand All @@ -51,50 +29,4 @@ protected function __construct()
}

abstract protected function aggregateId(): string;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is that not part of the event sourced trait?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess because a trait cannot contain abstract methods to force the using class to implement it

@Xerkus Any ideas how to improve this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh was late yesterday. Need to revert my comment. A trait can contain abstract methods ...


/**
* Get pending events and reset stack
*
* @return AggregateChanged[]
*/
protected function popRecordedEvents(): array
{
$pendingEvents = $this->recordedEvents;

$this->recordedEvents = [];

return $pendingEvents;
}

/**
* Record an aggregate changed event
*/
protected function recordThat(AggregateChanged $event): void
{
$this->version += 1;

$this->recordedEvents[] = $event->withVersion($this->version);

$this->apply($event);
}

/**
* Replay past events
*
* @throws RuntimeException
*/
protected function replay(Iterator $historyEvents): void
{
foreach ($historyEvents as $pastEvent) {
/** @var AggregateChanged $pastEvent */
$this->version = $pastEvent->version();

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

/**
* Apply given event
*/
abstract protected function apply(AggregateChanged $event): void;
}
117 changes: 117 additions & 0 deletions src/EventStoreIntegration/ClosureAggregateTranslator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<?php
/**
* This file is part of the prooph/event-sourcing.
* (c) 2014-2017 prooph software GmbH <contact@prooph.de>
* (c) 2015-2017 Sascha-Oliver Prolic <saschaprolic@googlemail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Prooph\EventSourcing\EventStoreIntegration;

use Iterator;
use Prooph\Common\Messaging\Message;
use Prooph\EventSourcing\Aggregate\AggregateTranslator as EventStoreAggregateTranslator;
use Prooph\EventSourcing\Aggregate\AggregateType;
use RuntimeException;

final class ClosureAggregateTranslator implements EventStoreAggregateTranslator
{
protected $aggregateIdExtractor;
protected $aggregateReconstructor;
protected $pendingEventsExtractor;
protected $replayStreamEvents;
protected $versionExtractor;

/**
* @param object $eventSourcedAggregateRoot
*
* @return int
*/
public function extractAggregateVersion($eventSourcedAggregateRoot): int
{
if (null === $this->versionExtractor) {
$this->versionExtractor = function (): int {
return $this->version;
};
}

return $this->versionExtractor->call($eventSourcedAggregateRoot);
}

/**
* @param object $anEventSourcedAggregateRoot
*
* @return string
*/
public function extractAggregateId($anEventSourcedAggregateRoot): string
{
if (null === $this->aggregateIdExtractor) {
$this->aggregateIdExtractor = function (): string {
return $this->aggregateId();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is the problem from above.

It is no longer guaranteed and the user gets no hint that such an aggregateId method is required by the translator.
The least thing we could do is add a method exists check and throw a meaningful exception instead of fatal but still not the best experience for the user.

};
}

return $this->aggregateIdExtractor->call($anEventSourcedAggregateRoot);
}

/**
* @param AggregateType $aggregateType
* @param Iterator $historyEvents
*
* @return object reconstructed AggregateRoot
*/
public function reconstituteAggregateFromHistory(AggregateType $aggregateType, Iterator $historyEvents)
{
if (null === $this->aggregateReconstructor) {
$this->aggregateReconstructor = function ($historyEvents) {
return static::reconstituteFromHistory($historyEvents);
};
}

$arClass = $aggregateType->toString();

if (! class_exists($arClass)) {
throw new RuntimeException(
sprintf('Aggregate root class %s cannot be found', $arClass)
);
}

return ($this->aggregateReconstructor->bindTo(null, $arClass))($historyEvents);
}

/**
* @param object $anEventSourcedAggregateRoot
*
* @return Message[]
*/
public function extractPendingStreamEvents($anEventSourcedAggregateRoot): array
{
if (null === $this->pendingEventsExtractor) {
$this->pendingEventsExtractor = function (): array {
return $this->popRecordedEvents();
};
}

return $this->pendingEventsExtractor->call($anEventSourcedAggregateRoot);
}

/**
* @param object $anEventSourcedAggregateRoot
* @param Iterator $events
*
* @return void
*/
public function replayStreamEvents($anEventSourcedAggregateRoot, Iterator $events): void
{
if (null === $this->replayStreamEvents) {
$this->replayStreamEvents = function ($events): void {
$this->replay($events);
};
}
$this->replayStreamEvents->call($anEventSourcedAggregateRoot, $events);
}
}
2 changes: 1 addition & 1 deletion tests/EventStoreIntegration/AggregateTranslatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ protected function resetRepository()
{
$this->repository = new AggregateRepository(
$this->eventStore,
AggregateType::fromAggregateRootClass('ProophTest\EventSourcing\Mock\User'),
AggregateType::fromAggregateRootClass(User::class),
new AggregateTranslator()
);
}
Expand Down
Loading