From 9f22c603092f97c70deb18ce2a826ab7c759882e Mon Sep 17 00:00:00 2001 From: Joshua Estes Date: Sun, 5 Nov 2023 13:50:37 -0500 Subject: [PATCH 1/3] [EventSourcing] New Version --- .github/CODEOWNERS | 1 + .github/labeler.yml | 1 + .../event-sourcing/aggregates/aggregate-id.md | 65 ++++++++++++++ .../aggregates/aggregate-version.md | 26 ++++++ .../event-sourcing/aggregates/index.md | 84 ------------------- docs/contracts/index.md | 2 +- mkdocs.yml | 2 + .../Aggregate/AbstractAggregate.php | 28 ++++--- .../Aggregate/AbstractAggregateId.php | 17 +++- .../Aggregate/AggregateIdInterface.php | 13 +-- .../Aggregate/AggregateInterface.php | 4 +- .../SnapshotableAggregateInterface.php | 8 +- .../AggregateClassMetadataInterface.php | 37 ++++++++ .../AggregateManagerInterface.php | 29 +++++++ .../EventSourcing/Attributes/AggregateId.php | 22 +++++ .../Attributes/AggregateVersion.php | 22 +++++ .../EventSourcing/Attributes/ApplyEvent.php | 26 ++++++ .../EventSourcing/Attributes/AsAggregate.php | 19 +++++ .../Attributes/AsAggregateId.php | 19 +++++ .../Attributes/AsAggregateRepository.php | 19 +++++ .../Attributes/AsAggregateVersion.php | 19 +++++ .../Attributes/AsEnricherHandler.php | 19 +++++ .../Attributes/AsEnricherProvider.php | 19 +++++ .../EventSourcing/Attributes/AsMessage.php | 19 +++++ .../Attributes/AsMessageProvider.php | 19 +++++ .../Attributes/AsMessageRepository.php | 19 +++++ .../EventSourcing/Attributes/AsSerializer.php | 19 +++++ .../Attributes/AsUpcasterHandler.php | 19 +++++ .../Attributes/AsUpcasterProvider.php | 19 +++++ .../EventSourcing/Message/AbstractMessage.php | 2 +- .../Message/MessageInterface.php | 2 +- .../Tests/Aggregate/AbstractAggregateTest.php | 20 +++++ .../Component/EventSourcing/composer.json | 3 + src/SonsOfPHP/Contract/EventSourcing/LICENSE | 19 +++++ .../Contract/EventSourcing/README.md | 16 ++++ .../Contract/EventSourcing/composer.json | 45 ++++++++++ 36 files changed, 603 insertions(+), 119 deletions(-) create mode 100644 docs/components/event-sourcing/aggregates/aggregate-id.md create mode 100644 docs/components/event-sourcing/aggregates/aggregate-version.md create mode 100644 src/SonsOfPHP/Component/EventSourcing/AggregateClassMetadataInterface.php create mode 100644 src/SonsOfPHP/Component/EventSourcing/AggregateManagerInterface.php create mode 100644 src/SonsOfPHP/Component/EventSourcing/Attributes/AggregateId.php create mode 100644 src/SonsOfPHP/Component/EventSourcing/Attributes/AggregateVersion.php create mode 100644 src/SonsOfPHP/Component/EventSourcing/Attributes/ApplyEvent.php create mode 100644 src/SonsOfPHP/Component/EventSourcing/Attributes/AsAggregate.php create mode 100644 src/SonsOfPHP/Component/EventSourcing/Attributes/AsAggregateId.php create mode 100644 src/SonsOfPHP/Component/EventSourcing/Attributes/AsAggregateRepository.php create mode 100644 src/SonsOfPHP/Component/EventSourcing/Attributes/AsAggregateVersion.php create mode 100644 src/SonsOfPHP/Component/EventSourcing/Attributes/AsEnricherHandler.php create mode 100644 src/SonsOfPHP/Component/EventSourcing/Attributes/AsEnricherProvider.php create mode 100644 src/SonsOfPHP/Component/EventSourcing/Attributes/AsMessage.php create mode 100644 src/SonsOfPHP/Component/EventSourcing/Attributes/AsMessageProvider.php create mode 100644 src/SonsOfPHP/Component/EventSourcing/Attributes/AsMessageRepository.php create mode 100644 src/SonsOfPHP/Component/EventSourcing/Attributes/AsSerializer.php create mode 100644 src/SonsOfPHP/Component/EventSourcing/Attributes/AsUpcasterHandler.php create mode 100644 src/SonsOfPHP/Component/EventSourcing/Attributes/AsUpcasterProvider.php create mode 100644 src/SonsOfPHP/Contract/EventSourcing/LICENSE create mode 100644 src/SonsOfPHP/Contract/EventSourcing/README.md create mode 100644 src/SonsOfPHP/Contract/EventSourcing/composer.json diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 8fa1a3b0..01fc51f6 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -33,3 +33,4 @@ /src/SonsOfPHP/Component/Queue @JoshuaEstes /src/SonsOfPHP/Component/Version @JoshuaEstes /src/SonsOfPHP/Contract/Core @JoshuaEstes +/src/SonsOfPHP/Contract/EventSourcing @JoshuaEstes diff --git a/.github/labeler.yml b/.github/labeler.yml index d4062aca..62bb4d48 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -56,3 +56,4 @@ Contracts: documentation: - docs/** + - mkdocs.yml diff --git a/docs/components/event-sourcing/aggregates/aggregate-id.md b/docs/components/event-sourcing/aggregates/aggregate-id.md new file mode 100644 index 00000000..0e22a2bf --- /dev/null +++ b/docs/components/event-sourcing/aggregates/aggregate-id.md @@ -0,0 +1,65 @@ +--- +title: Aggregate IDs +--- + +# Aggregate IDs + +Each Aggregate will need to have it's own unique ID. You can use the same +AggregateId class for all your aggregates or you can make you own for each one +to ensure data consistency. + +### Working with an Aggregate ID + +```php +toString(); +$id = (string) $aggregateId; + +// To compare two Aggregate IDs +if ($aggregateId->equals($anotherAggregateId)) { + // they are the same +} +``` + +If you need to create an Aggregate ID Class in order to use type-hinting, just +extend the `AbstractAggregateId` class. + +```php +toString()); // true + +// It also still supports setting the ID via the constructor +$ulid = new AggregateId((string) new Ulid()); +``` diff --git a/docs/components/event-sourcing/aggregates/aggregate-version.md b/docs/components/event-sourcing/aggregates/aggregate-version.md new file mode 100644 index 00000000..a6db60e4 --- /dev/null +++ b/docs/components/event-sourcing/aggregates/aggregate-version.md @@ -0,0 +1,26 @@ +--- +title: Aggregate Versions +--- + +# Aggregate Version + +An Aggregate also has a version. Each event that is raised will increase the +version. You will generally not have to work much with versions as they are +mostly handled internally. + +### Working with an Aggregate Version + +```php +toInt(); + +// Comparing Versions +if ($aggregateVersion->equals($anotherAggregateVersion)) { + // they are the same +} +``` diff --git a/docs/components/event-sourcing/aggregates/index.md b/docs/components/event-sourcing/aggregates/index.md index 1e931979..f14c83d6 100644 --- a/docs/components/event-sourcing/aggregates/index.md +++ b/docs/components/event-sourcing/aggregates/index.md @@ -6,90 +6,6 @@ title: Aggregates Aggregates are the primary objects that you will be working with. -## Aggregate ID - -Each Aggregate will need to have it's own unique ID. You can use the same -AggregateId class for all your aggregates or you can make you own for each one -to ensure data consistency. - -### Working with an Aggregate ID - -```php -toString(); -$id = (string) $aggregateId; - -// To compare two Aggregate IDs -if ($aggregateId->equals($anotherAggregateId)) { - // they are the same -} -``` - -If you need to create an Aggregate ID Class in order to use type-hinting, just -extend the `AbstractAggregateId` class. - -```php -toString()); // true - -// It also still supports setting the ID via the constructor -$ulid = new AggregateId((string) new Ulid()); -``` - - -## Aggregate Version - -An Aggregate also has a version. Each event that is raised will increase the -version. You will generally not have to work much with versions as they are -mostly handled internally. - -### Working with an Aggregate Version - -```php -toInt(); - -// Comparing Versions -if ($aggregateVersion->equals($anotherAggregateVersion)) { - // they are the same -} -``` - ## Creating an Aggregate Pretty simple to create an aggregate. diff --git a/docs/contracts/index.md b/docs/contracts/index.md index 7a160344..57600bb3 100644 --- a/docs/contracts/index.md +++ b/docs/contracts/index.md @@ -8,7 +8,7 @@ Contracts are interfaces that can be reusable across multiple different libraries and projects. The actual implementation of the interfaces is left up to you. -The interfaces also enhance existing PSRs. +The interfaces may also enhance existing PSRs. Whenever possible, the components and projects created by Sons of PHP will implement these interfaces. diff --git a/mkdocs.yml b/mkdocs.yml index 01d84f2f..12affce6 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -71,6 +71,8 @@ nav: - components/event-sourcing/index.md - Aggregates: - components/event-sourcing/aggregates/index.md + - Aggregate ID: components/event-sourcing/aggregates/aggregate-id.md + - Aggregate Version: components/event-sourcing/aggregates/aggregate-version.md - Storage: - components/event-sourcing/aggregates/storage/index.md - Event Messages: diff --git a/src/SonsOfPHP/Component/EventSourcing/Aggregate/AbstractAggregate.php b/src/SonsOfPHP/Component/EventSourcing/Aggregate/AbstractAggregate.php index fbd98229..6ade4968 100644 --- a/src/SonsOfPHP/Component/EventSourcing/Aggregate/AbstractAggregate.php +++ b/src/SonsOfPHP/Component/EventSourcing/Aggregate/AbstractAggregate.php @@ -27,23 +27,16 @@ final public function __construct(AggregateIdInterface|string $id) } /** - * @param AggregateIdInterface|string $id - * - * @return static + * {@inheritdoc} */ - final public static function new($id) - { - @trigger_error(sprintf('"%s::new()" is deprecated, use "new %s()" instead.', static::class, static::class), \E_USER_DEPRECATED); - $static = new static($id); - - return $static; - } - final public function getAggregateId(): AggregateIdInterface { return $this->id; } + /** + * {@inheritdoc} + */ final public function getAggregateVersion(): AggregateVersionInterface { return $this->version; @@ -59,6 +52,9 @@ final public function hasPendingEvents(): bool return \count($this->pendingEvents) > 0; } + /** + * {@inheritdoc} + */ final public function getPendingEvents(): iterable { $events = $this->pendingEvents; @@ -67,12 +63,18 @@ final public function getPendingEvents(): iterable return $events; } + /** + * {@inheritdoc} + */ final public function peekPendingEvents(): iterable { return $this->pendingEvents; } - final public static function buildFromEvents(AggregateIdInterface $id, \Generator $events): AggregateInterface + /** + * {@inheritdoc} + */ + final public static function buildFromEvents(AggregateIdInterface $id, iterable $events): AggregateInterface { $aggregate = new static($id); foreach ($events as $event) { @@ -124,7 +126,7 @@ final protected function applyEvent(MessageInterface $event): void $method = 'apply' . end($parts); if (method_exists($this, $method)) { - $this->{$method}($event); // @phpstan-ignore-line + $this->{$method}($event); } $this->version = $this->version->next(); diff --git a/src/SonsOfPHP/Component/EventSourcing/Aggregate/AbstractAggregateId.php b/src/SonsOfPHP/Component/EventSourcing/Aggregate/AbstractAggregateId.php index 96af6b80..1216d27f 100644 --- a/src/SonsOfPHP/Component/EventSourcing/Aggregate/AbstractAggregateId.php +++ b/src/SonsOfPHP/Component/EventSourcing/Aggregate/AbstractAggregateId.php @@ -24,21 +24,30 @@ public function __construct( } } + /** + * {@inheritdoc} + */ final public function __toString(): string { return $this->toString(); } - final public function toString(): string + final public static function fromString(string $id): AggregateIdInterface { - return $this->id; + return new static($id); } - final public static function fromString(string $id): AggregateIdInterface + /** + * {@inheritdoc} + */ + final public function toString(): string { - return new static($id); + return $this->id; } + /** + * {@inheritdoc} + */ final public function equals(AggregateIdInterface $that): bool { return $this->toString() === $that->toString(); diff --git a/src/SonsOfPHP/Component/EventSourcing/Aggregate/AggregateIdInterface.php b/src/SonsOfPHP/Component/EventSourcing/Aggregate/AggregateIdInterface.php index 85a40f59..df3b2cb7 100644 --- a/src/SonsOfPHP/Component/EventSourcing/Aggregate/AggregateIdInterface.php +++ b/src/SonsOfPHP/Component/EventSourcing/Aggregate/AggregateIdInterface.php @@ -32,22 +32,11 @@ public function __toString(): string; */ public function toString(): string; - /** - * Creates an instance of AggregateIdInterface with the passed in - * value. - * - * Example: - * $id = AggregateId::fromString('unique-uuid'); - * - * @throws EventSourcingException - */ - public static function fromString(string $id): self; - /** * Compares two Aggregate ID objects and returns true if they are * equal. * * @throws EventSourcingException */ - public function equals(self $that): bool; + public function equals(AggregateIdInterface $that): bool; } diff --git a/src/SonsOfPHP/Component/EventSourcing/Aggregate/AggregateInterface.php b/src/SonsOfPHP/Component/EventSourcing/Aggregate/AggregateInterface.php index f69bab38..d5f04cfc 100644 --- a/src/SonsOfPHP/Component/EventSourcing/Aggregate/AggregateInterface.php +++ b/src/SonsOfPHP/Component/EventSourcing/Aggregate/AggregateInterface.php @@ -47,9 +47,9 @@ public function peekPendingEvents(): iterable; /** * Build Aggregate from a collection of Domain Events. * - * @param \Generator $events yields MessageInterface objects + * @param iterable $events yields MessageInterface objects * * @throws EventSourcingException */ - public static function buildFromEvents(AggregateIdInterface $id, \Generator $events): self; + public static function buildFromEvents(AggregateIdInterface $id, iterable $events): self; } diff --git a/src/SonsOfPHP/Component/EventSourcing/Aggregate/SnapshotableAggregateInterface.php b/src/SonsOfPHP/Component/EventSourcing/Aggregate/SnapshotableAggregateInterface.php index dea67c7e..f108d700 100644 --- a/src/SonsOfPHP/Component/EventSourcing/Aggregate/SnapshotableAggregateInterface.php +++ b/src/SonsOfPHP/Component/EventSourcing/Aggregate/SnapshotableAggregateInterface.php @@ -16,9 +16,15 @@ */ interface SnapshotableAggregateInterface extends AggregateInterface { + /** + */ public function createSnapshot(): SnapshotInterface; + /** + */ public static function buildFromSnapshot(SnapshotInterface $snapshot): self; - public static function buildFromSnapshotAndEvents(SnapshotInterface $snapshot, \Generator $events): self; + /** + */ + public static function buildFromSnapshotAndEvents(SnapshotInterface $snapshot, iterable $events): self; } diff --git a/src/SonsOfPHP/Component/EventSourcing/AggregateClassMetadataInterface.php b/src/SonsOfPHP/Component/EventSourcing/AggregateClassMetadataInterface.php new file mode 100644 index 00000000..c1d28ba9 --- /dev/null +++ b/src/SonsOfPHP/Component/EventSourcing/AggregateClassMetadataInterface.php @@ -0,0 +1,37 @@ + + */ +interface AggregateClassMetadataInterface +{ + // aggregate class name + public function getName(): string; + + // property name for aggregate id + public function getAggregateId(): string; + + // property name for aggregate version + public function getAggregateVersion(): string; + + public function getReflectinClass(): \ReflectionClass; + + // If any upserters, return them + //public function getUpserters(): iterable; + + // returns the serializer to use + //public function getSerializer(): MessageSerializerInterface; + + //public function getAggregateRepository(): AggregateRepositoryInterface; + + //public function getMessageRepository(): MessageRepositoryInterface; + + //public function getMessageProviderInterface(): MessageProviderInterface; +} diff --git a/src/SonsOfPHP/Component/EventSourcing/AggregateManagerInterface.php b/src/SonsOfPHP/Component/EventSourcing/AggregateManagerInterface.php new file mode 100644 index 00000000..7c57bc16 --- /dev/null +++ b/src/SonsOfPHP/Component/EventSourcing/AggregateManagerInterface.php @@ -0,0 +1,29 @@ +find(UserAggregate::class, 'unique-id'); + * ... + * $manager->persist($aggregate); + * + * @author Joshua Estes + */ +interface AggregateManagerInterface +{ + /** + * Usage: + * $manager->find(UserAggregate::class, 'unique-id'); + */ + public function find(AggregateInterface $class, AggregateIdInterface|string $id): ?AggregateInterface; + + public function persist(AggregateInterface $aggregate): void; +} diff --git a/src/SonsOfPHP/Component/EventSourcing/Attributes/AggregateId.php b/src/SonsOfPHP/Component/EventSourcing/Attributes/AggregateId.php new file mode 100644 index 00000000..a568cb0e --- /dev/null +++ b/src/SonsOfPHP/Component/EventSourcing/Attributes/AggregateId.php @@ -0,0 +1,22 @@ + + */ +#[Attribute(Attribute::TARGET_PROPERTY)] +final class AggregateId +{ +} diff --git a/src/SonsOfPHP/Component/EventSourcing/Attributes/AggregateVersion.php b/src/SonsOfPHP/Component/EventSourcing/Attributes/AggregateVersion.php new file mode 100644 index 00000000..e94355fe --- /dev/null +++ b/src/SonsOfPHP/Component/EventSourcing/Attributes/AggregateVersion.php @@ -0,0 +1,22 @@ + + */ +#[Attribute(Attribute::TARGET_PROPERTY)] +final class AggregateVersion +{ +} diff --git a/src/SonsOfPHP/Component/EventSourcing/Attributes/ApplyEvent.php b/src/SonsOfPHP/Component/EventSourcing/Attributes/ApplyEvent.php new file mode 100644 index 00000000..3facb3c9 --- /dev/null +++ b/src/SonsOfPHP/Component/EventSourcing/Attributes/ApplyEvent.php @@ -0,0 +1,26 @@ + + */ +#[Attribute(Attribute::TARGET_METHOD)] +final class ApplyEvent +{ + /** + * @codeCoverageIgnore + */ + public function __construct(public readonly string $event) {} +} diff --git a/src/SonsOfPHP/Component/EventSourcing/Attributes/AsAggregate.php b/src/SonsOfPHP/Component/EventSourcing/Attributes/AsAggregate.php new file mode 100644 index 00000000..07f56b0b --- /dev/null +++ b/src/SonsOfPHP/Component/EventSourcing/Attributes/AsAggregate.php @@ -0,0 +1,19 @@ + + */ +#[Attribute(Attribute::TARGET_CLASS)] +final class AsAggregate +{ +} diff --git a/src/SonsOfPHP/Component/EventSourcing/Attributes/AsAggregateId.php b/src/SonsOfPHP/Component/EventSourcing/Attributes/AsAggregateId.php new file mode 100644 index 00000000..1a9aa01e --- /dev/null +++ b/src/SonsOfPHP/Component/EventSourcing/Attributes/AsAggregateId.php @@ -0,0 +1,19 @@ + + */ +#[Attribute(Attribute::TARGET_CLASS)] +final class AsAggregateId +{ +} diff --git a/src/SonsOfPHP/Component/EventSourcing/Attributes/AsAggregateRepository.php b/src/SonsOfPHP/Component/EventSourcing/Attributes/AsAggregateRepository.php new file mode 100644 index 00000000..cdbc0625 --- /dev/null +++ b/src/SonsOfPHP/Component/EventSourcing/Attributes/AsAggregateRepository.php @@ -0,0 +1,19 @@ + + */ +#[Attribute(Attribute::TARGET_CLASS)] +final class AsAggregateRepository +{ +} diff --git a/src/SonsOfPHP/Component/EventSourcing/Attributes/AsAggregateVersion.php b/src/SonsOfPHP/Component/EventSourcing/Attributes/AsAggregateVersion.php new file mode 100644 index 00000000..20cf7c54 --- /dev/null +++ b/src/SonsOfPHP/Component/EventSourcing/Attributes/AsAggregateVersion.php @@ -0,0 +1,19 @@ + + */ +#[Attribute(Attribute::TARGET_CLASS)] +final class AsAggregateVersion +{ +} diff --git a/src/SonsOfPHP/Component/EventSourcing/Attributes/AsEnricherHandler.php b/src/SonsOfPHP/Component/EventSourcing/Attributes/AsEnricherHandler.php new file mode 100644 index 00000000..ced43967 --- /dev/null +++ b/src/SonsOfPHP/Component/EventSourcing/Attributes/AsEnricherHandler.php @@ -0,0 +1,19 @@ + + */ +#[Attribute(Attribute::TARGET_CLASS)] +final class AsEnricherHandler +{ +} diff --git a/src/SonsOfPHP/Component/EventSourcing/Attributes/AsEnricherProvider.php b/src/SonsOfPHP/Component/EventSourcing/Attributes/AsEnricherProvider.php new file mode 100644 index 00000000..2c730105 --- /dev/null +++ b/src/SonsOfPHP/Component/EventSourcing/Attributes/AsEnricherProvider.php @@ -0,0 +1,19 @@ + + */ +#[Attribute(Attribute::TARGET_CLASS)] +final class AsEnricherProvider +{ +} diff --git a/src/SonsOfPHP/Component/EventSourcing/Attributes/AsMessage.php b/src/SonsOfPHP/Component/EventSourcing/Attributes/AsMessage.php new file mode 100644 index 00000000..8c12e871 --- /dev/null +++ b/src/SonsOfPHP/Component/EventSourcing/Attributes/AsMessage.php @@ -0,0 +1,19 @@ + + */ +#[Attribute(Attribute::TARGET_CLASS)] +final class AsMessage +{ +} diff --git a/src/SonsOfPHP/Component/EventSourcing/Attributes/AsMessageProvider.php b/src/SonsOfPHP/Component/EventSourcing/Attributes/AsMessageProvider.php new file mode 100644 index 00000000..df4e32b8 --- /dev/null +++ b/src/SonsOfPHP/Component/EventSourcing/Attributes/AsMessageProvider.php @@ -0,0 +1,19 @@ + + */ +#[Attribute(Attribute::TARGET_CLASS)] +final class AsMessageProvider +{ +} diff --git a/src/SonsOfPHP/Component/EventSourcing/Attributes/AsMessageRepository.php b/src/SonsOfPHP/Component/EventSourcing/Attributes/AsMessageRepository.php new file mode 100644 index 00000000..b28fcc01 --- /dev/null +++ b/src/SonsOfPHP/Component/EventSourcing/Attributes/AsMessageRepository.php @@ -0,0 +1,19 @@ + + */ +#[Attribute(Attribute::TARGET_CLASS)] +final class AsMessageRepository +{ +} diff --git a/src/SonsOfPHP/Component/EventSourcing/Attributes/AsSerializer.php b/src/SonsOfPHP/Component/EventSourcing/Attributes/AsSerializer.php new file mode 100644 index 00000000..d7ba95a5 --- /dev/null +++ b/src/SonsOfPHP/Component/EventSourcing/Attributes/AsSerializer.php @@ -0,0 +1,19 @@ + + */ +#[Attribute(Attribute::TARGET_CLASS)] +final class AsSerializer +{ +} diff --git a/src/SonsOfPHP/Component/EventSourcing/Attributes/AsUpcasterHandler.php b/src/SonsOfPHP/Component/EventSourcing/Attributes/AsUpcasterHandler.php new file mode 100644 index 00000000..5bda59a1 --- /dev/null +++ b/src/SonsOfPHP/Component/EventSourcing/Attributes/AsUpcasterHandler.php @@ -0,0 +1,19 @@ + + */ +#[Attribute(Attribute::TARGET_CLASS)] +final class AsUpcasterHandler +{ +} diff --git a/src/SonsOfPHP/Component/EventSourcing/Attributes/AsUpcasterProvider.php b/src/SonsOfPHP/Component/EventSourcing/Attributes/AsUpcasterProvider.php new file mode 100644 index 00000000..6050bac4 --- /dev/null +++ b/src/SonsOfPHP/Component/EventSourcing/Attributes/AsUpcasterProvider.php @@ -0,0 +1,19 @@ + + */ +#[Attribute(Attribute::TARGET_CLASS)] +final class AsUpcasterProvider +{ +} diff --git a/src/SonsOfPHP/Component/EventSourcing/Message/AbstractMessage.php b/src/SonsOfPHP/Component/EventSourcing/Message/AbstractMessage.php index 89ff924b..5717534a 100644 --- a/src/SonsOfPHP/Component/EventSourcing/Message/AbstractMessage.php +++ b/src/SonsOfPHP/Component/EventSourcing/Message/AbstractMessage.php @@ -27,7 +27,7 @@ final public function __construct( private MessageMetadata $metadata = new MessageMetadata(), ) {} - final public static function new(): MessageInterface + final public static function new(): static { return new static(new MessagePayload(), new MessageMetadata()); } diff --git a/src/SonsOfPHP/Component/EventSourcing/Message/MessageInterface.php b/src/SonsOfPHP/Component/EventSourcing/Message/MessageInterface.php index 540e7892..a92ae396 100644 --- a/src/SonsOfPHP/Component/EventSourcing/Message/MessageInterface.php +++ b/src/SonsOfPHP/Component/EventSourcing/Message/MessageInterface.php @@ -25,7 +25,7 @@ interface MessageInterface * * @return static */ - public static function new(): self; + public static function new(): static; /** * Returns the Aggregate ID, if the aggregate ID is unknown, it diff --git a/src/SonsOfPHP/Component/EventSourcing/Tests/Aggregate/AbstractAggregateTest.php b/src/SonsOfPHP/Component/EventSourcing/Tests/Aggregate/AbstractAggregateTest.php index 37423234..55a2d5c8 100644 --- a/src/SonsOfPHP/Component/EventSourcing/Tests/Aggregate/AbstractAggregateTest.php +++ b/src/SonsOfPHP/Component/EventSourcing/Tests/Aggregate/AbstractAggregateTest.php @@ -91,6 +91,26 @@ public function testItWillRaiseExceptionWithInvalidId(): void $this->getMockForAbstractClass(AbstractAggregate::class, [new \stdClass()]); } + /** + * @covers ::__construct + */ + public function testConstructWorksWithStrings(): void + { + $aggregate = $this->getMockForAbstractClass(AbstractAggregate::class, ['unique-id']); + + $this->assertSame('unique-id', $aggregate->getAggregateId()->toString()); + } + + /** + * @covers ::__construct + */ + public function testConstructWorksWithAggregateId(): void + { + $aggregate = $this->getMockForAbstractClass(AbstractAggregate::class, [new AggregateId('unique-id')]); + + $this->assertSame('unique-id', $aggregate->getAggregateId()->toString()); + } + /** * @covers ::peekPendingEvents */ diff --git a/src/SonsOfPHP/Component/EventSourcing/composer.json b/src/SonsOfPHP/Component/EventSourcing/composer.json index 4a60f666..50671b17 100644 --- a/src/SonsOfPHP/Component/EventSourcing/composer.json +++ b/src/SonsOfPHP/Component/EventSourcing/composer.json @@ -36,6 +36,9 @@ "require-dev": { "phpunit/phpunit": "^10.4" }, + "provide": { + "sonsofphp/event-sourcing-implementation": "^0.3.x-dev" + }, "suggest": { "sonsofphp/event-sourcing-doctrine": "Adds additional functionality using Doctrine", "sonsofphp/event-sourcing-symfony": "Adds additional functionality using Symfony Components" diff --git a/src/SonsOfPHP/Contract/EventSourcing/LICENSE b/src/SonsOfPHP/Contract/EventSourcing/LICENSE new file mode 100644 index 00000000..39238382 --- /dev/null +++ b/src/SonsOfPHP/Contract/EventSourcing/LICENSE @@ -0,0 +1,19 @@ +Copyright 2022 to Present Joshua Estes + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/SonsOfPHP/Contract/EventSourcing/README.md b/src/SonsOfPHP/Contract/EventSourcing/README.md new file mode 100644 index 00000000..9a69cdc5 --- /dev/null +++ b/src/SonsOfPHP/Contract/EventSourcing/README.md @@ -0,0 +1,16 @@ +Sons of PHP - Event Sourcing Contract +===================================== + +## Learn More + +* [Documentation][docs] +* [Contributing][contributing] +* [Report Issues][issues] and [Submit Pull Requests][pull-requests] in the [Mother Repository][mother-repo] +* Get Help & Support using [Discussions][discussions] + +[discussions]: https://github.com/orgs/SonsOfPHP/discussions +[mother-repo]: https://github.com/SonsOfPHP/sonsofphp +[contributing]: https://docs.sonsofphp.com/contributing/ +[docs]: https://docs.sonsofphp.com/components/http-message/ +[issues]: https://github.com/SonsOfPHP/sonsofphp/issues?q=is%3Aopen+is%3Aissue+label%3AEventSourcing +[pull-requests]: https://github.com/SonsOfPHP/sonsofphp/pulls?q=is%3Aopen+is%3Apr+label%3AEventSourcing diff --git a/src/SonsOfPHP/Contract/EventSourcing/composer.json b/src/SonsOfPHP/Contract/EventSourcing/composer.json new file mode 100644 index 00000000..80d0eaae --- /dev/null +++ b/src/SonsOfPHP/Contract/EventSourcing/composer.json @@ -0,0 +1,45 @@ +{ + "name": "sonsofphp/event-sourcing-contract", + "type": "library", + "description": "Event Sourcing Contracts", + "keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"], + "homepage": "https://github.com/SonsOfPHP/event-sourcing-contract", + "license": "MIT", + "authors": [ + { + "name": "Joshua Estes", + "email": "joshua@sonsofphp.com" + } + ], + "support": { + "issues": "https://github.com/SonsOfPHP/sonsofphp/issues", + "forum": "https://github.com/orgs/SonsOfPHP/discussions", + "docs": "https://docs.sonsofphp.com" + }, + "autoload": { + "psr-4": { + "SonsOfPHP\\Contract\\EventSourcing\\": "" + } + }, + "minimum-stability": "dev", + "prefer-stable": true, + "require": { + "php": ">=8.1" + }, + "extra": { + "sort-packages": true, + "branch-alias": { + "dev-main": "0.3.x-dev" + } + }, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/JoshuaEstes" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/packagist-sonsofphp-sonsofphp" + } + ] +} From d27ad01179503b92213e20c6f9d67d60bf5913ca Mon Sep 17 00:00:00 2001 From: Joshua Estes Date: Sun, 5 Nov 2023 16:35:15 -0500 Subject: [PATCH 2/3] updates --- .../EventSourcing/AggregateClassMetadata.php | 42 ++++++++++++++ .../AggregateClassMetadataInterface.php | 8 +-- .../EventSourcing/AggregateManager.php | 55 +++++++++++++++++++ .../Component/EventSourcing/Configuration.php | 32 +++++++++++ .../EventSourcing/ConfigurationInterface.php | 13 +++++ .../{Attributes => Mapping}/AggregateId.php | 2 +- .../AggregateVersion.php | 2 +- .../{Attributes => Mapping}/ApplyEvent.php | 2 +- .../{Attributes => Mapping}/AsAggregate.php | 2 +- .../{Attributes => Mapping}/AsAggregateId.php | 2 +- .../AsAggregateRepository.php | 2 +- .../AsAggregateVersion.php | 2 +- .../AsEnricherHandler.php | 2 +- .../AsEnricherProvider.php | 2 +- .../{Attributes => Mapping}/AsMessage.php | 2 +- .../AsMessageProvider.php | 2 +- .../AsMessageRepository.php | 2 +- .../{Attributes => Mapping}/AsSerializer.php | 2 +- .../AsUpcasterHandler.php | 2 +- .../AsUpcasterProvider.php | 2 +- .../Mapping/Driver/AttributeDriver.php | 55 +++++++++++++++++++ .../Mapping/Driver/DriverInterface.php | 13 +++++ .../Tests/AggregateClassMetadataTest.php | 35 ++++++++++++ .../Tests/AggregateManagerTest.php | 54 ++++++++++++++++++ .../EventSourcing/Tests/ConfigurationTest.php | 23 ++++++++ .../EventSourcing/Tests/FakeAggregate.php | 10 ++++ .../Mapping/Driver/AttributeDriverTest.php | 40 ++++++++++++++ 27 files changed, 391 insertions(+), 19 deletions(-) create mode 100644 src/SonsOfPHP/Component/EventSourcing/AggregateClassMetadata.php create mode 100644 src/SonsOfPHP/Component/EventSourcing/AggregateManager.php create mode 100644 src/SonsOfPHP/Component/EventSourcing/Configuration.php create mode 100644 src/SonsOfPHP/Component/EventSourcing/ConfigurationInterface.php rename src/SonsOfPHP/Component/EventSourcing/{Attributes => Mapping}/AggregateId.php (83%) rename src/SonsOfPHP/Component/EventSourcing/{Attributes => Mapping}/AggregateVersion.php (84%) rename src/SonsOfPHP/Component/EventSourcing/{Attributes => Mapping}/ApplyEvent.php (88%) rename src/SonsOfPHP/Component/EventSourcing/{Attributes => Mapping}/AsAggregate.php (80%) rename src/SonsOfPHP/Component/EventSourcing/{Attributes => Mapping}/AsAggregateId.php (80%) rename src/SonsOfPHP/Component/EventSourcing/{Attributes => Mapping}/AsAggregateRepository.php (82%) rename src/SonsOfPHP/Component/EventSourcing/{Attributes => Mapping}/AsAggregateVersion.php (81%) rename src/SonsOfPHP/Component/EventSourcing/{Attributes => Mapping}/AsEnricherHandler.php (82%) rename src/SonsOfPHP/Component/EventSourcing/{Attributes => Mapping}/AsEnricherProvider.php (82%) rename src/SonsOfPHP/Component/EventSourcing/{Attributes => Mapping}/AsMessage.php (79%) rename src/SonsOfPHP/Component/EventSourcing/{Attributes => Mapping}/AsMessageProvider.php (81%) rename src/SonsOfPHP/Component/EventSourcing/{Attributes => Mapping}/AsMessageRepository.php (82%) rename src/SonsOfPHP/Component/EventSourcing/{Attributes => Mapping}/AsSerializer.php (81%) rename src/SonsOfPHP/Component/EventSourcing/{Attributes => Mapping}/AsUpcasterHandler.php (81%) rename src/SonsOfPHP/Component/EventSourcing/{Attributes => Mapping}/AsUpcasterProvider.php (81%) create mode 100644 src/SonsOfPHP/Component/EventSourcing/Mapping/Driver/AttributeDriver.php create mode 100644 src/SonsOfPHP/Component/EventSourcing/Mapping/Driver/DriverInterface.php create mode 100644 src/SonsOfPHP/Component/EventSourcing/Tests/AggregateClassMetadataTest.php create mode 100644 src/SonsOfPHP/Component/EventSourcing/Tests/AggregateManagerTest.php create mode 100644 src/SonsOfPHP/Component/EventSourcing/Tests/ConfigurationTest.php create mode 100644 src/SonsOfPHP/Component/EventSourcing/Tests/Mapping/Driver/AttributeDriverTest.php diff --git a/src/SonsOfPHP/Component/EventSourcing/AggregateClassMetadata.php b/src/SonsOfPHP/Component/EventSourcing/AggregateClassMetadata.php new file mode 100644 index 00000000..b723a6c5 --- /dev/null +++ b/src/SonsOfPHP/Component/EventSourcing/AggregateClassMetadata.php @@ -0,0 +1,42 @@ + + */ +final class AggregateClassMetadata implements AggregateClassMetadataInterface +{ + public function __construct( + private string $name, + ) {} + + public function getName(): string + { + return $this->name; + } + + public function getReflectionClass(): \ReflectionClass + { + return new \ReflectionClass($this->getName()); + } + + public function getAggregateRepositoryClass(): AggregateRepositoryInterface + { + return AggregateRepository::class; + } + + public function getMessageRepositoryClass(): MessageRepositoryInterface + { + return InMemoryMessageRepository::class; + } +} diff --git a/src/SonsOfPHP/Component/EventSourcing/AggregateClassMetadataInterface.php b/src/SonsOfPHP/Component/EventSourcing/AggregateClassMetadataInterface.php index c1d28ba9..fc0bca0c 100644 --- a/src/SonsOfPHP/Component/EventSourcing/AggregateClassMetadataInterface.php +++ b/src/SonsOfPHP/Component/EventSourcing/AggregateClassMetadataInterface.php @@ -13,15 +13,15 @@ interface AggregateClassMetadataInterface { // aggregate class name - public function getName(): string; + //public function getName(): string; // property name for aggregate id - public function getAggregateId(): string; + //public function getAggregateId(): string; // property name for aggregate version - public function getAggregateVersion(): string; + //public function getAggregateVersion(): string; - public function getReflectinClass(): \ReflectionClass; + //public function getReflectinClass(): \ReflectionClass; // If any upserters, return them //public function getUpserters(): iterable; diff --git a/src/SonsOfPHP/Component/EventSourcing/AggregateManager.php b/src/SonsOfPHP/Component/EventSourcing/AggregateManager.php new file mode 100644 index 00000000..402d5d4e --- /dev/null +++ b/src/SonsOfPHP/Component/EventSourcing/AggregateManager.php @@ -0,0 +1,55 @@ + + */ +final class AggregateManager implements AggregateManagerInterface +{ + private array $aggregates = []; + + public function __construct( + private ConfigurationInterface $config, + private ContainerInterface $container, + ) {} + + public function registerAggregate(string $aggregate) + { + foreach ($this->config->getDriver()->getClassAttributes($aggregate) as $attribute) { + if ($attribute instanceof AsAggregate) { + $this->aggregates[$aggregate] = new AggregateClassMetadata($aggregate); + } + } + } + + public function find(AggregateInterface $class, AggregateIdInterface|string $id): ?AggregateInterface + { + if (!array_key_exists($class, $this->aggregates)) { + throw new \Exception('aggregate not registered'); + } + + return $this->container->get($this->aggregate[$class]->getAggregateRepositoryClass())->find($id); + } + + public function persist(AggregateInterface $aggregate): void + { + if (!array_key_exists($class, $this->aggregates)) { + throw new \Exception('aggregate not registered'); + } + + $events = $aggregate->getPendingEvents(); + if (0 === count($events)) { + return; + } + + $this->container->get($this->aggregate[$class]->getMessageRepositoryClass())->persist($aggregate); + } +} diff --git a/src/SonsOfPHP/Component/EventSourcing/Configuration.php b/src/SonsOfPHP/Component/EventSourcing/Configuration.php new file mode 100644 index 00000000..f17703a7 --- /dev/null +++ b/src/SonsOfPHP/Component/EventSourcing/Configuration.php @@ -0,0 +1,32 @@ + + */ +final class Configuration implements ConfigurationInterface +{ + public function __construct( + private array $paths = [], + private DriverInterface $driver = new AttributeDriver(), + private EventDispatcherInterface $eventDispatcher = new EventDispatcher(), + ) {} + + public function getDriver(): DriverInterface + { + return $this->driver; + } + + public function getEventDispatcher(): EventDispatcherInterface + { + return $this->eventDispatcher; + } +} diff --git a/src/SonsOfPHP/Component/EventSourcing/ConfigurationInterface.php b/src/SonsOfPHP/Component/EventSourcing/ConfigurationInterface.php new file mode 100644 index 00000000..f29b4437 --- /dev/null +++ b/src/SonsOfPHP/Component/EventSourcing/ConfigurationInterface.php @@ -0,0 +1,13 @@ + + */ +interface ConfigurationInterface +{ + public function getDriver(); +} diff --git a/src/SonsOfPHP/Component/EventSourcing/Attributes/AggregateId.php b/src/SonsOfPHP/Component/EventSourcing/Mapping/AggregateId.php similarity index 83% rename from src/SonsOfPHP/Component/EventSourcing/Attributes/AggregateId.php rename to src/SonsOfPHP/Component/EventSourcing/Mapping/AggregateId.php index a568cb0e..ff873351 100644 --- a/src/SonsOfPHP/Component/EventSourcing/Attributes/AggregateId.php +++ b/src/SonsOfPHP/Component/EventSourcing/Mapping/AggregateId.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace SonsOfPHP\Component\EventSourcing\Attributes; +namespace SonsOfPHP\Component\EventSourcing\Mapping; use Attribute; diff --git a/src/SonsOfPHP/Component/EventSourcing/Attributes/AggregateVersion.php b/src/SonsOfPHP/Component/EventSourcing/Mapping/AggregateVersion.php similarity index 84% rename from src/SonsOfPHP/Component/EventSourcing/Attributes/AggregateVersion.php rename to src/SonsOfPHP/Component/EventSourcing/Mapping/AggregateVersion.php index e94355fe..7495bb67 100644 --- a/src/SonsOfPHP/Component/EventSourcing/Attributes/AggregateVersion.php +++ b/src/SonsOfPHP/Component/EventSourcing/Mapping/AggregateVersion.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace SonsOfPHP\Component\EventSourcing\Attributes; +namespace SonsOfPHP\Component\EventSourcing\Mapping; use Attribute; diff --git a/src/SonsOfPHP/Component/EventSourcing/Attributes/ApplyEvent.php b/src/SonsOfPHP/Component/EventSourcing/Mapping/ApplyEvent.php similarity index 88% rename from src/SonsOfPHP/Component/EventSourcing/Attributes/ApplyEvent.php rename to src/SonsOfPHP/Component/EventSourcing/Mapping/ApplyEvent.php index 3facb3c9..7af339f7 100644 --- a/src/SonsOfPHP/Component/EventSourcing/Attributes/ApplyEvent.php +++ b/src/SonsOfPHP/Component/EventSourcing/Mapping/ApplyEvent.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace SonsOfPHP\Component\EventSourcing\Attributes; +namespace SonsOfPHP\Component\EventSourcing\Mapping; use Attribute; diff --git a/src/SonsOfPHP/Component/EventSourcing/Attributes/AsAggregate.php b/src/SonsOfPHP/Component/EventSourcing/Mapping/AsAggregate.php similarity index 80% rename from src/SonsOfPHP/Component/EventSourcing/Attributes/AsAggregate.php rename to src/SonsOfPHP/Component/EventSourcing/Mapping/AsAggregate.php index 07f56b0b..9f7cfff7 100644 --- a/src/SonsOfPHP/Component/EventSourcing/Attributes/AsAggregate.php +++ b/src/SonsOfPHP/Component/EventSourcing/Mapping/AsAggregate.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace SonsOfPHP\Component\EventSourcing\Attributes; +namespace SonsOfPHP\Component\EventSourcing\Mapping; use Attribute; diff --git a/src/SonsOfPHP/Component/EventSourcing/Attributes/AsAggregateId.php b/src/SonsOfPHP/Component/EventSourcing/Mapping/AsAggregateId.php similarity index 80% rename from src/SonsOfPHP/Component/EventSourcing/Attributes/AsAggregateId.php rename to src/SonsOfPHP/Component/EventSourcing/Mapping/AsAggregateId.php index 1a9aa01e..b0f38ffb 100644 --- a/src/SonsOfPHP/Component/EventSourcing/Attributes/AsAggregateId.php +++ b/src/SonsOfPHP/Component/EventSourcing/Mapping/AsAggregateId.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace SonsOfPHP\Component\EventSourcing\Attributes; +namespace SonsOfPHP\Component\EventSourcing\Mapping; use Attribute; diff --git a/src/SonsOfPHP/Component/EventSourcing/Attributes/AsAggregateRepository.php b/src/SonsOfPHP/Component/EventSourcing/Mapping/AsAggregateRepository.php similarity index 82% rename from src/SonsOfPHP/Component/EventSourcing/Attributes/AsAggregateRepository.php rename to src/SonsOfPHP/Component/EventSourcing/Mapping/AsAggregateRepository.php index cdbc0625..dca4be87 100644 --- a/src/SonsOfPHP/Component/EventSourcing/Attributes/AsAggregateRepository.php +++ b/src/SonsOfPHP/Component/EventSourcing/Mapping/AsAggregateRepository.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace SonsOfPHP\Component\EventSourcing\Attributes; +namespace SonsOfPHP\Component\EventSourcing\Mapping; use Attribute; diff --git a/src/SonsOfPHP/Component/EventSourcing/Attributes/AsAggregateVersion.php b/src/SonsOfPHP/Component/EventSourcing/Mapping/AsAggregateVersion.php similarity index 81% rename from src/SonsOfPHP/Component/EventSourcing/Attributes/AsAggregateVersion.php rename to src/SonsOfPHP/Component/EventSourcing/Mapping/AsAggregateVersion.php index 20cf7c54..cf7cdeac 100644 --- a/src/SonsOfPHP/Component/EventSourcing/Attributes/AsAggregateVersion.php +++ b/src/SonsOfPHP/Component/EventSourcing/Mapping/AsAggregateVersion.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace SonsOfPHP\Component\EventSourcing\Attributes; +namespace SonsOfPHP\Component\EventSourcing\Mapping; use Attribute; diff --git a/src/SonsOfPHP/Component/EventSourcing/Attributes/AsEnricherHandler.php b/src/SonsOfPHP/Component/EventSourcing/Mapping/AsEnricherHandler.php similarity index 82% rename from src/SonsOfPHP/Component/EventSourcing/Attributes/AsEnricherHandler.php rename to src/SonsOfPHP/Component/EventSourcing/Mapping/AsEnricherHandler.php index ced43967..05c34e53 100644 --- a/src/SonsOfPHP/Component/EventSourcing/Attributes/AsEnricherHandler.php +++ b/src/SonsOfPHP/Component/EventSourcing/Mapping/AsEnricherHandler.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace SonsOfPHP\Component\EventSourcing\Attributes; +namespace SonsOfPHP\Component\EventSourcing\Mapping; use Attribute; diff --git a/src/SonsOfPHP/Component/EventSourcing/Attributes/AsEnricherProvider.php b/src/SonsOfPHP/Component/EventSourcing/Mapping/AsEnricherProvider.php similarity index 82% rename from src/SonsOfPHP/Component/EventSourcing/Attributes/AsEnricherProvider.php rename to src/SonsOfPHP/Component/EventSourcing/Mapping/AsEnricherProvider.php index 2c730105..8eb8dac7 100644 --- a/src/SonsOfPHP/Component/EventSourcing/Attributes/AsEnricherProvider.php +++ b/src/SonsOfPHP/Component/EventSourcing/Mapping/AsEnricherProvider.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace SonsOfPHP\Component\EventSourcing\Attributes; +namespace SonsOfPHP\Component\EventSourcing\Mapping; use Attribute; diff --git a/src/SonsOfPHP/Component/EventSourcing/Attributes/AsMessage.php b/src/SonsOfPHP/Component/EventSourcing/Mapping/AsMessage.php similarity index 79% rename from src/SonsOfPHP/Component/EventSourcing/Attributes/AsMessage.php rename to src/SonsOfPHP/Component/EventSourcing/Mapping/AsMessage.php index 8c12e871..a6580912 100644 --- a/src/SonsOfPHP/Component/EventSourcing/Attributes/AsMessage.php +++ b/src/SonsOfPHP/Component/EventSourcing/Mapping/AsMessage.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace SonsOfPHP\Component\EventSourcing\Attributes; +namespace SonsOfPHP\Component\EventSourcing\Mapping; use Attribute; diff --git a/src/SonsOfPHP/Component/EventSourcing/Attributes/AsMessageProvider.php b/src/SonsOfPHP/Component/EventSourcing/Mapping/AsMessageProvider.php similarity index 81% rename from src/SonsOfPHP/Component/EventSourcing/Attributes/AsMessageProvider.php rename to src/SonsOfPHP/Component/EventSourcing/Mapping/AsMessageProvider.php index df4e32b8..26357a2f 100644 --- a/src/SonsOfPHP/Component/EventSourcing/Attributes/AsMessageProvider.php +++ b/src/SonsOfPHP/Component/EventSourcing/Mapping/AsMessageProvider.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace SonsOfPHP\Component\EventSourcing\Attributes; +namespace SonsOfPHP\Component\EventSourcing\Mapping; use Attribute; diff --git a/src/SonsOfPHP/Component/EventSourcing/Attributes/AsMessageRepository.php b/src/SonsOfPHP/Component/EventSourcing/Mapping/AsMessageRepository.php similarity index 82% rename from src/SonsOfPHP/Component/EventSourcing/Attributes/AsMessageRepository.php rename to src/SonsOfPHP/Component/EventSourcing/Mapping/AsMessageRepository.php index b28fcc01..e9a110b5 100644 --- a/src/SonsOfPHP/Component/EventSourcing/Attributes/AsMessageRepository.php +++ b/src/SonsOfPHP/Component/EventSourcing/Mapping/AsMessageRepository.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace SonsOfPHP\Component\EventSourcing\Attributes; +namespace SonsOfPHP\Component\EventSourcing\Mapping; use Attribute; diff --git a/src/SonsOfPHP/Component/EventSourcing/Attributes/AsSerializer.php b/src/SonsOfPHP/Component/EventSourcing/Mapping/AsSerializer.php similarity index 81% rename from src/SonsOfPHP/Component/EventSourcing/Attributes/AsSerializer.php rename to src/SonsOfPHP/Component/EventSourcing/Mapping/AsSerializer.php index d7ba95a5..2ce2dc10 100644 --- a/src/SonsOfPHP/Component/EventSourcing/Attributes/AsSerializer.php +++ b/src/SonsOfPHP/Component/EventSourcing/Mapping/AsSerializer.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace SonsOfPHP\Component\EventSourcing\Attributes; +namespace SonsOfPHP\Component\EventSourcing\Mapping; use Attribute; diff --git a/src/SonsOfPHP/Component/EventSourcing/Attributes/AsUpcasterHandler.php b/src/SonsOfPHP/Component/EventSourcing/Mapping/AsUpcasterHandler.php similarity index 81% rename from src/SonsOfPHP/Component/EventSourcing/Attributes/AsUpcasterHandler.php rename to src/SonsOfPHP/Component/EventSourcing/Mapping/AsUpcasterHandler.php index 5bda59a1..ece50619 100644 --- a/src/SonsOfPHP/Component/EventSourcing/Attributes/AsUpcasterHandler.php +++ b/src/SonsOfPHP/Component/EventSourcing/Mapping/AsUpcasterHandler.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace SonsOfPHP\Component\EventSourcing\Attributes; +namespace SonsOfPHP\Component\EventSourcing\Mapping; use Attribute; diff --git a/src/SonsOfPHP/Component/EventSourcing/Attributes/AsUpcasterProvider.php b/src/SonsOfPHP/Component/EventSourcing/Mapping/AsUpcasterProvider.php similarity index 81% rename from src/SonsOfPHP/Component/EventSourcing/Attributes/AsUpcasterProvider.php rename to src/SonsOfPHP/Component/EventSourcing/Mapping/AsUpcasterProvider.php index 6050bac4..53bb9f30 100644 --- a/src/SonsOfPHP/Component/EventSourcing/Attributes/AsUpcasterProvider.php +++ b/src/SonsOfPHP/Component/EventSourcing/Mapping/AsUpcasterProvider.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace SonsOfPHP\Component\EventSourcing\Attributes; +namespace SonsOfPHP\Component\EventSourcing\Mapping; use Attribute; diff --git a/src/SonsOfPHP/Component/EventSourcing/Mapping/Driver/AttributeDriver.php b/src/SonsOfPHP/Component/EventSourcing/Mapping/Driver/AttributeDriver.php new file mode 100644 index 00000000..9424b805 --- /dev/null +++ b/src/SonsOfPHP/Component/EventSourcing/Mapping/Driver/AttributeDriver.php @@ -0,0 +1,55 @@ + + */ +final class AttributeDriver implements DriverInterface +{ + public function getReflectionClass($class) + { + return new \ReflectionClass($class); + } + + public function getClassAttributes($class): iterable + { + foreach ($this->getReflectionClass($class)->getAttributes() as $refAttribute) { + // @todo only return supported attributes + yield $refAttribute->newInstance(); + } + } + + public function getMethodAttributes($class): iterable + { + foreach ($this->getReflectionClass($class)->getMethods() as $refMethod) { + foreach ($refMethod->getAttributes() as $attribute) { + // @todo only return supported attributes + yield $refMethod->getName() => $attribute->newInstance(); + } + } + } + + public function getPropertyAttributes($class): iterable + { + foreach ($this->getReflectionClass($class)->getProperties() as $refProperty) { + foreach ($refProperty->getAttributes() as $attribute) { + // @todo only return supported attributes + yield $refProperty->getName() => $attribute->newInstance(); + } + } + } + + public function getPropertyAttribute($class, $property) + { + foreach ($this->getPropertyAttributes($class) as $i => $attribute) { + if ($i === $property) { + return $attribute; + } + } + + return null; + } +} diff --git a/src/SonsOfPHP/Component/EventSourcing/Mapping/Driver/DriverInterface.php b/src/SonsOfPHP/Component/EventSourcing/Mapping/Driver/DriverInterface.php new file mode 100644 index 00000000..2f3a1d60 --- /dev/null +++ b/src/SonsOfPHP/Component/EventSourcing/Mapping/Driver/DriverInterface.php @@ -0,0 +1,13 @@ + + */ +interface DriverInterface +{ + //public function getClassAttributes(): array; +} diff --git a/src/SonsOfPHP/Component/EventSourcing/Tests/AggregateClassMetadataTest.php b/src/SonsOfPHP/Component/EventSourcing/Tests/AggregateClassMetadataTest.php new file mode 100644 index 00000000..1d969caf --- /dev/null +++ b/src/SonsOfPHP/Component/EventSourcing/Tests/AggregateClassMetadataTest.php @@ -0,0 +1,35 @@ +assertInstanceOf(AggregateClassMetadataInterface::class, new AggregateClassMetadata(FakeAggregate::class)); + } + + /** + * @coversNothing + */ + public function testItWorks(): void + { + $metadata = new AggregateClassMetadata(FakeAggregate::class); + + //dd($metadata); + $this->assertTrue(true); + } +} diff --git a/src/SonsOfPHP/Component/EventSourcing/Tests/AggregateManagerTest.php b/src/SonsOfPHP/Component/EventSourcing/Tests/AggregateManagerTest.php new file mode 100644 index 00000000..d8f9912f --- /dev/null +++ b/src/SonsOfPHP/Component/EventSourcing/Tests/AggregateManagerTest.php @@ -0,0 +1,54 @@ +config = $this->createMock(ConfigurationInterface::class); + $this->container = $this->createMock(ContainerInterface::class); + } + + /** + * @coversNothing + */ + public function testItImplementsCorrectInterface(): void + { + $this->assertInstanceOf(AggregateManagerInterface::class, new AggregateManager($this->config, $this->container)); + } + + /** + * @coversNothing + */ + public function testItWorks(): void + { + $this->config->method('getDriver')->willReturn(new AttributeDriver()); + + $manager = new AggregateManager($this->config, $this->container); + + $manager->registerAggregate(FakeAggregate::class); + + + + + + $this->assertTrue(true); + } +} diff --git a/src/SonsOfPHP/Component/EventSourcing/Tests/ConfigurationTest.php b/src/SonsOfPHP/Component/EventSourcing/Tests/ConfigurationTest.php new file mode 100644 index 00000000..de0c317f --- /dev/null +++ b/src/SonsOfPHP/Component/EventSourcing/Tests/ConfigurationTest.php @@ -0,0 +1,23 @@ +assertInstanceOf(ConfigurationInterface::class, new Configuration()); + } +} diff --git a/src/SonsOfPHP/Component/EventSourcing/Tests/FakeAggregate.php b/src/SonsOfPHP/Component/EventSourcing/Tests/FakeAggregate.php index 5d9785fc..bec4610c 100644 --- a/src/SonsOfPHP/Component/EventSourcing/Tests/FakeAggregate.php +++ b/src/SonsOfPHP/Component/EventSourcing/Tests/FakeAggregate.php @@ -5,11 +5,21 @@ namespace SonsOfPHP\Component\EventSourcing\Tests; use SonsOfPHP\Component\EventSourcing\Aggregate\AbstractAggregate; +use SonsOfPHP\Component\EventSourcing\Mapping\AsAggregate; +use SonsOfPHP\Component\EventSourcing\Mapping\AggregateId; +use SonsOfPHP\Component\EventSourcing\Mapping\ApplyEvent; +#[AsAggregate] class FakeAggregate extends AbstractAggregate { + #[AggregateId] + public $id; + public function raiseThisEvent($event): void { $this->raiseEvent($event); } + + #[ApplyEvent('stdClass')] + protected function applyExampleEvent($event) {} } diff --git a/src/SonsOfPHP/Component/EventSourcing/Tests/Mapping/Driver/AttributeDriverTest.php b/src/SonsOfPHP/Component/EventSourcing/Tests/Mapping/Driver/AttributeDriverTest.php new file mode 100644 index 00000000..b0f9fbe5 --- /dev/null +++ b/src/SonsOfPHP/Component/EventSourcing/Tests/Mapping/Driver/AttributeDriverTest.php @@ -0,0 +1,40 @@ +assertInstanceOf(DriverInterface::class, new AttributeDriver()); + } + + /** + * @coversNothing + */ + public function testItWorks(): void + { + $driver = new AttributeDriver(); + + //dd( + // iterator_to_array($driver->getClassAttributes(FakeAggregate::class)), + // iterator_to_array($driver->getMethodAttributes(FakeAggregate::class)), + // iterator_to_array($driver->getPropertyAttributes(FakeAggregate::class)), + // $driver->getPropertyAttribute(FakeAggregate::class, 'id'), + //); + $this->assertTrue(true); + } +} From 39f6009889d29f8af93e79615551592f2977c564 Mon Sep 17 00:00:00 2001 From: Joshua Estes Date: Sun, 5 Nov 2023 16:48:59 -0500 Subject: [PATCH 3/3] meh --- .../EventSourcing/AggregateManager.php | 14 +++++---- .../Component/EventSourcing/Configuration.php | 31 ++++++++++++++++--- .../Tests/AggregateManagerTest.php | 11 ++----- 3 files changed, 37 insertions(+), 19 deletions(-) diff --git a/src/SonsOfPHP/Component/EventSourcing/AggregateManager.php b/src/SonsOfPHP/Component/EventSourcing/AggregateManager.php index 402d5d4e..2ea055a8 100644 --- a/src/SonsOfPHP/Component/EventSourcing/AggregateManager.php +++ b/src/SonsOfPHP/Component/EventSourcing/AggregateManager.php @@ -18,31 +18,33 @@ final class AggregateManager implements AggregateManagerInterface public function __construct( private ConfigurationInterface $config, - private ContainerInterface $container, ) {} - public function registerAggregate(string $aggregate) + public function registerAggregate(string $aggregate): void { foreach ($this->config->getDriver()->getClassAttributes($aggregate) as $attribute) { if ($attribute instanceof AsAggregate) { $this->aggregates[$aggregate] = new AggregateClassMetadata($aggregate); + return; } } + + throw new \Exception('Invalid Aggregate'); } public function find(AggregateInterface $class, AggregateIdInterface|string $id): ?AggregateInterface { if (!array_key_exists($class, $this->aggregates)) { - throw new \Exception('aggregate not registered'); + $this->registerAggregate($aggregate); } - return $this->container->get($this->aggregate[$class]->getAggregateRepositoryClass())->find($id); + return $this->config->getContainer()->get($this->aggregate[$class]->getAggregateRepositoryClass())->find($id); } public function persist(AggregateInterface $aggregate): void { if (!array_key_exists($class, $this->aggregates)) { - throw new \Exception('aggregate not registered'); + $this->registerAggregate($aggregate); } $events = $aggregate->getPendingEvents(); @@ -50,6 +52,6 @@ public function persist(AggregateInterface $aggregate): void return; } - $this->container->get($this->aggregate[$class]->getMessageRepositoryClass())->persist($aggregate); + $this->config->getContainer()->get($this->aggregate[$class]->getMessageRepositoryClass())->persist($aggregate); } } diff --git a/src/SonsOfPHP/Component/EventSourcing/Configuration.php b/src/SonsOfPHP/Component/EventSourcing/Configuration.php index f17703a7..c1117df9 100644 --- a/src/SonsOfPHP/Component/EventSourcing/Configuration.php +++ b/src/SonsOfPHP/Component/EventSourcing/Configuration.php @@ -14,11 +14,29 @@ */ final class Configuration implements ConfigurationInterface { + private ContainerInterface $container; + private DriverInterface $driver; + private EventDispatcherInterface $eventDispatcher; + public function __construct( - private array $paths = [], - private DriverInterface $driver = new AttributeDriver(), - private EventDispatcherInterface $eventDispatcher = new EventDispatcher(), - ) {} + array $config = [], + ) { + if (array_key_exists('container', $config)) { + $this->container = $config['container']; + } + + if (array_key_exists('driver', $config)) { + $this->driver = $config['driver']; + } else { + $this->driver = new AttributeDriver(); + } + + if (array_key_exists('event_dispatcher', $config)) { + $this->eventDispatcher = $config['event_dispatcher']; + } else { + $this->eventDispatcher = new EventDispatcher(); + } + } public function getDriver(): DriverInterface { @@ -29,4 +47,9 @@ public function getEventDispatcher(): EventDispatcherInterface { return $this->eventDispatcher; } + + public function getContainer(): ContainerInterface + { + return $this->container; + } } diff --git a/src/SonsOfPHP/Component/EventSourcing/Tests/AggregateManagerTest.php b/src/SonsOfPHP/Component/EventSourcing/Tests/AggregateManagerTest.php index d8f9912f..217c15d1 100644 --- a/src/SonsOfPHP/Component/EventSourcing/Tests/AggregateManagerTest.php +++ b/src/SonsOfPHP/Component/EventSourcing/Tests/AggregateManagerTest.php @@ -18,12 +18,10 @@ final class AggregateManagerTest extends TestCase { private $config; - private $container; public function setUp(): void { $this->config = $this->createMock(ConfigurationInterface::class); - $this->container = $this->createMock(ContainerInterface::class); } /** @@ -31,7 +29,7 @@ public function setUp(): void */ public function testItImplementsCorrectInterface(): void { - $this->assertInstanceOf(AggregateManagerInterface::class, new AggregateManager($this->config, $this->container)); + $this->assertInstanceOf(AggregateManagerInterface::class, new AggregateManager($this->config)); } /** @@ -41,14 +39,9 @@ public function testItWorks(): void { $this->config->method('getDriver')->willReturn(new AttributeDriver()); - $manager = new AggregateManager($this->config, $this->container); - + $manager = new AggregateManager($this->config); $manager->registerAggregate(FakeAggregate::class); - - - - $this->assertTrue(true); } }