From 6ae86316720d4bd130037ebd05a46739d6bd6ad3 Mon Sep 17 00:00:00 2001 From: David Badura Date: Wed, 1 Dec 2021 12:19:34 +0100 Subject: [PATCH 1/2] add strict apply method & extract the default apply method --- src/Aggregate/AggregateRoot.php | 25 +------- src/Aggregate/ApplyMethodNotFound.php | 21 +++++++ src/Aggregate/DefaultApplyMethod.php | 31 ++++++++++ src/Aggregate/StrictApplyMethod.php | 31 ++++++++++ .../BasicImplementation/Aggregate/Profile.php | 3 + .../Pipeline/Aggregate/Profile.php | 3 + .../AggregateRootWithStrictApplyTest.php | 61 +++++++++++++++++++ tests/Unit/Fixture/Profile.php | 3 + tests/Unit/Fixture/ProfileWithSnapshot.php | 3 + tests/Unit/Fixture/ProfileWithStrictApply.php | 58 ++++++++++++++++++ 10 files changed, 216 insertions(+), 23 deletions(-) create mode 100644 src/Aggregate/ApplyMethodNotFound.php create mode 100644 src/Aggregate/DefaultApplyMethod.php create mode 100644 src/Aggregate/StrictApplyMethod.php create mode 100644 tests/Unit/Aggregate/AggregateRootWithStrictApplyTest.php create mode 100644 tests/Unit/Fixture/ProfileWithStrictApply.php diff --git a/src/Aggregate/AggregateRoot.php b/src/Aggregate/AggregateRoot.php index 71ef1e43..ba316981 100644 --- a/src/Aggregate/AggregateRoot.php +++ b/src/Aggregate/AggregateRoot.php @@ -4,11 +4,6 @@ namespace Patchlevel\EventSourcing\Aggregate; -use function end; -use function explode; -use function get_class; -use function method_exists; - abstract class AggregateRoot { /** @var array */ @@ -23,6 +18,8 @@ final protected function __construct() abstract public function aggregateRootId(): string; + abstract protected function apply(AggregateChanged $event): void; + protected function record(AggregateChanged $event): void { $this->playhead++; @@ -61,24 +58,6 @@ public static function createFromEventStream(array $stream): self return $self; } - protected function apply(AggregateChanged $event): void - { - $method = $this->findApplyMethod($event); - - if (!method_exists($this, $method)) { - return; - } - - $this->$method($event); - } - - private function findApplyMethod(AggregateChanged $event): string - { - $classParts = explode('\\', get_class($event)); - - return 'apply' . end($classParts); - } - public function playhead(): int { return $this->playhead; diff --git a/src/Aggregate/ApplyMethodNotFound.php b/src/Aggregate/ApplyMethodNotFound.php new file mode 100644 index 00000000..4916061c --- /dev/null +++ b/src/Aggregate/ApplyMethodNotFound.php @@ -0,0 +1,21 @@ +findApplyMethod($event); + + if (!method_exists($this, $method)) { + return; + } + + $this->$method($event); + } + + private function findApplyMethod(AggregateChanged $event): string + { + $classParts = explode('\\', get_class($event)); + + return 'apply' . end($classParts); + } +} diff --git a/src/Aggregate/StrictApplyMethod.php b/src/Aggregate/StrictApplyMethod.php new file mode 100644 index 00000000..dd95d8df --- /dev/null +++ b/src/Aggregate/StrictApplyMethod.php @@ -0,0 +1,31 @@ +findApplyMethod($event); + + if (!method_exists($this, $method)) { + throw new ApplyMethodNotFound($this, $event, $method); + } + + $this->$method($event); + } + + private function findApplyMethod(AggregateChanged $event): string + { + $classParts = explode('\\', get_class($event)); + + return 'apply' . end($classParts); + } +} diff --git a/tests/Integration/BasicImplementation/Aggregate/Profile.php b/tests/Integration/BasicImplementation/Aggregate/Profile.php index 3b390c2b..f4dde4c1 100644 --- a/tests/Integration/BasicImplementation/Aggregate/Profile.php +++ b/tests/Integration/BasicImplementation/Aggregate/Profile.php @@ -4,11 +4,14 @@ namespace Patchlevel\EventSourcing\Tests\Integration\BasicImplementation\Aggregate; +use Patchlevel\EventSourcing\Aggregate\DefaultApplyMethod; use Patchlevel\EventSourcing\Aggregate\SnapshotableAggregateRoot; use Patchlevel\EventSourcing\Tests\Integration\BasicImplementation\Events\ProfileCreated; final class Profile extends SnapshotableAggregateRoot { + use DefaultApplyMethod; + private string $id; public function aggregateRootId(): string diff --git a/tests/Integration/Pipeline/Aggregate/Profile.php b/tests/Integration/Pipeline/Aggregate/Profile.php index 271d77c6..34765be2 100644 --- a/tests/Integration/Pipeline/Aggregate/Profile.php +++ b/tests/Integration/Pipeline/Aggregate/Profile.php @@ -5,6 +5,7 @@ namespace Patchlevel\EventSourcing\Tests\Integration\Pipeline\Aggregate; use Patchlevel\EventSourcing\Aggregate\AggregateRoot; +use Patchlevel\EventSourcing\Aggregate\DefaultApplyMethod; use Patchlevel\EventSourcing\Tests\Integration\Pipeline\Events\NewVisited; use Patchlevel\EventSourcing\Tests\Integration\Pipeline\Events\OldVisited; use Patchlevel\EventSourcing\Tests\Integration\Pipeline\Events\PrivacyAdded; @@ -12,6 +13,8 @@ final class Profile extends AggregateRoot { + use DefaultApplyMethod; + private string $id; private bool $privacy; private int $visited; diff --git a/tests/Unit/Aggregate/AggregateRootWithStrictApplyTest.php b/tests/Unit/Aggregate/AggregateRootWithStrictApplyTest.php new file mode 100644 index 00000000..b16abef6 --- /dev/null +++ b/tests/Unit/Aggregate/AggregateRootWithStrictApplyTest.php @@ -0,0 +1,61 @@ +aggregateRootId()); + self::assertEquals(1, $profile->playhead()); + self::assertEquals($id, $profile->id()); + self::assertEquals($email, $profile->email()); + + $events = $profile->releaseEvents(); + + self::assertCount(1, $events); + $event = $events[0]; + self::assertEquals(1, $event->playhead()); + } + + public function testEventWithoutApplyMethod(): void + { + $this->expectException(ApplyMethodNotFound::class); + + $profileId = ProfileId::fromString('1'); + $email = Email::fromString('david.badura@patchlevel.de'); + + $messageId = MessageId::fromString('2'); + + $profile = ProfileWithStrictApply::createProfile($profileId, $email); + + $events = $profile->releaseEvents(); + + self::assertCount(1, $events); + self::assertEquals(1, $profile->playhead()); + $event = $events[0]; + self::assertEquals(1, $event->playhead()); + + $profile->publishMessage( + Message::create( + $messageId, + 'foo' + ) + ); + } +} diff --git a/tests/Unit/Fixture/Profile.php b/tests/Unit/Fixture/Profile.php index a66cb563..0952346f 100644 --- a/tests/Unit/Fixture/Profile.php +++ b/tests/Unit/Fixture/Profile.php @@ -5,9 +5,12 @@ namespace Patchlevel\EventSourcing\Tests\Unit\Fixture; use Patchlevel\EventSourcing\Aggregate\AggregateRoot; +use Patchlevel\EventSourcing\Aggregate\DefaultApplyMethod; final class Profile extends AggregateRoot { + use DefaultApplyMethod; + private ProfileId $id; private Email $email; /** @var array */ diff --git a/tests/Unit/Fixture/ProfileWithSnapshot.php b/tests/Unit/Fixture/ProfileWithSnapshot.php index 7ebc44e9..3017a29d 100644 --- a/tests/Unit/Fixture/ProfileWithSnapshot.php +++ b/tests/Unit/Fixture/ProfileWithSnapshot.php @@ -4,10 +4,13 @@ namespace Patchlevel\EventSourcing\Tests\Unit\Fixture; +use Patchlevel\EventSourcing\Aggregate\DefaultApplyMethod; use Patchlevel\EventSourcing\Aggregate\SnapshotableAggregateRoot; final class ProfileWithSnapshot extends SnapshotableAggregateRoot { + use DefaultApplyMethod; + private ProfileId $id; private Email $email; /** @var array */ diff --git a/tests/Unit/Fixture/ProfileWithStrictApply.php b/tests/Unit/Fixture/ProfileWithStrictApply.php new file mode 100644 index 00000000..680c22f8 --- /dev/null +++ b/tests/Unit/Fixture/ProfileWithStrictApply.php @@ -0,0 +1,58 @@ +id; + } + + public function email(): Email + { + return $this->email; + } + + public static function createProfile(ProfileId $id, Email $email): self + { + $self = new self(); + $self->record(ProfileCreated::raise($id, $email)); + + return $self; + } + + public function publishMessage(Message $message): void + { + $this->record(MessagePublished::raise( + $this->id, + $message, + )); + } + + public function visitProfile(ProfileId $profileId): void + { + $this->record(ProfileVisited::raise($this->id, $profileId)); + } + + protected function applyProfileCreated(ProfileCreated $event): void + { + $this->id = $event->profileId(); + $this->email = $event->email(); + } + + public function aggregateRootId(): string + { + return $this->id->toString(); + } +} From ab11a0e2f93e8e08a6870e08c56271e85478b7a0 Mon Sep 17 00:00:00 2001 From: David Badura Date: Wed, 1 Dec 2021 12:48:24 +0100 Subject: [PATCH 2/2] add psalm requirements --- src/Aggregate/ApplyMethodNotFound.php | 2 +- src/Aggregate/DefaultApplyMethod.php | 3 +++ src/Aggregate/StrictApplyMethod.php | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Aggregate/ApplyMethodNotFound.php b/src/Aggregate/ApplyMethodNotFound.php index 4916061c..c193d2c9 100644 --- a/src/Aggregate/ApplyMethodNotFound.php +++ b/src/Aggregate/ApplyMethodNotFound.php @@ -9,7 +9,7 @@ class ApplyMethodNotFound extends AggregateException { - public function __construct(object $aggregate, AggregateChanged $event, string $method) + public function __construct(AggregateRoot $aggregate, AggregateChanged $event, string $method) { parent::__construct(sprintf( 'Apply method "%s::%s" could not be found for the event "%s"', diff --git a/src/Aggregate/DefaultApplyMethod.php b/src/Aggregate/DefaultApplyMethod.php index d25a42aa..f8057f11 100644 --- a/src/Aggregate/DefaultApplyMethod.php +++ b/src/Aggregate/DefaultApplyMethod.php @@ -9,6 +9,9 @@ use function get_class; use function method_exists; +/** + * @psalm-require-extends AggregateRoot + */ trait DefaultApplyMethod { protected function apply(AggregateChanged $event): void diff --git a/src/Aggregate/StrictApplyMethod.php b/src/Aggregate/StrictApplyMethod.php index dd95d8df..9850a7cc 100644 --- a/src/Aggregate/StrictApplyMethod.php +++ b/src/Aggregate/StrictApplyMethod.php @@ -9,6 +9,9 @@ use function get_class; use function method_exists; +/** + * @psalm-require-extends AggregateRoot + */ trait StrictApplyMethod { protected function apply(AggregateChanged $event): void