Skip to content

Commit

Permalink
Merge pull request #62 from Xerkus/feature/ar-traits
Browse files Browse the repository at this point in the history
Extract aggregate root into traits to make it easier to avoid domain extending infrastructure
  • Loading branch information
codeliner authored May 24, 2017
2 parents 8ad49e3 + cb50ee6 commit 43741b0
Show file tree
Hide file tree
Showing 7 changed files with 411 additions and 75 deletions.
26 changes: 26 additions & 0 deletions docs/inheritance.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,29 @@ then you can also just change the `aggregate_translator` key in your config to p
and register the `UserAggregateTranslator` in your container.

see also: http://www.sasaprolic.com/2016/02/inheritance-with-aggregate-roots-in.html

## Alternative to AggregateRoot inheritance

Abstract `Prooph\EventSourcing\AggregateRoot` class provides a solid basis for
your aggregate roots, however, it is not mandatory. Two traits,
`Prooph\EventSourcing\Aggregate\EventProducerTrait` and
`Prooph\EventSourcing\Aggregate\EventSourcedTrait`, together provide exactly
the same functionality.

- `EventProducerTrait` is responsible for event producing side of Event
Sourcing and might be used independently of `EventSourcedTrait` when you are
not ready to start with full event sourcing but still want to get the benefit
of design validation and audit trail provided by Event Sourcing. Forcing all
changes to be applied internally via event sourcing will ensure events data
consistency with the state and will make it easier to switch to full event
sourcing later on.

- `EventSourcedTrait` is responsible for restoring state from event stream, it
should be used together with `EventProducerTrait` as normally you will not be
applying events not produced by that aggregate root.

Default aggregate translator uses `AggregateRootDecorator` to access protected
methods of `Prooph\EventSourcing\AggregateRoot` descendants, you will need to
switch to
`Prooph\EventSourcing\EventStoreIntegration\ClosureAggregateTranslator` for
aggregate roots using traits.
65 changes: 65 additions & 0 deletions src/Aggregate/EventProducerTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php
/**
* This file is part of the prooph/event-sourcing.
* (c) 2014-2017 prooph software GmbH <[email protected]>
* (c) 2015-2017 Sascha-Oliver Prolic <[email protected]>
*
* 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);
}

abstract protected function aggregateId(): string;

/**
* Apply given event
*/
abstract protected function apply(AggregateChanged $event): void;
}
60 changes: 60 additions & 0 deletions src/Aggregate/EventSourcedTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php
/**
* This file is part of the prooph/event-sourcing.
* (c) 2014-2017 prooph software GmbH <[email protected]>
* (c) 2015-2017 Sascha-Oliver Prolic <[email protected]>
*
* 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);
}
}

abstract protected function aggregateId(): string;

/**
* Apply given event
*/
abstract protected function apply(AggregateChanged $event): void;
}
78 changes: 4 additions & 74 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 @@ -49,52 +27,4 @@ protected static function reconstituteFromHistory(Iterator $historyEvents): self
protected function __construct()
{
}

abstract protected function aggregateId(): string;

/**
* 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 <[email protected]>
* (c) 2015-2017 Sascha-Oliver Prolic <[email protected]>
*
* 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();
};
}

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

0 comments on commit 43741b0

Please sign in to comment.