From a87cad88495755790ddd3d159a86e341aa0747a1 Mon Sep 17 00:00:00 2001 From: Matthias Noback Date: Wed, 15 May 2019 15:21:14 +0200 Subject: [PATCH 1/3] Extract a few traits so we need less boilerplate code --- src/TalisOrm/Aggregate.php | 3 + src/TalisOrm/AggregateBehavior.php | 59 +++++++++++++++++++ src/TalisOrm/ChildEntity.php | 3 + src/TalisOrm/ChildEntityBehavior.php | 23 ++++++++ .../TalisOrm/AggregateRepositoryTest/Line.php | 18 +----- .../AggregateRepositoryTest/Order.php | 50 +--------------- 6 files changed, 93 insertions(+), 63 deletions(-) create mode 100644 src/TalisOrm/AggregateBehavior.php create mode 100644 src/TalisOrm/ChildEntityBehavior.php diff --git a/src/TalisOrm/Aggregate.php b/src/TalisOrm/Aggregate.php index 066a901..ff96076 100644 --- a/src/TalisOrm/Aggregate.php +++ b/src/TalisOrm/Aggregate.php @@ -4,6 +4,9 @@ use TalisOrm\DomainEvents\EventRecordingCapabilities; +/** + * @see AggregateBehavior + */ interface Aggregate extends Entity { /** diff --git a/src/TalisOrm/AggregateBehavior.php b/src/TalisOrm/AggregateBehavior.php new file mode 100644 index 0000000..6a57d1e --- /dev/null +++ b/src/TalisOrm/AggregateBehavior.php @@ -0,0 +1,59 @@ +deletedChildEntities; + + $this->deletedChildEntities = []; + + return $deletedChildEntities; + } + + private function deleteChildEntity(ChildEntity $childEntity) + { + $this->deletedChildEntities[] = $childEntity; + } + + public function isNew(): bool + { + return $this->isNew; + } + + public function markAsPersisted(): void + { + $this->isNew = false; + } + + /** + * @return int + */ + public function aggregateVersion() + { + return $this->aggregateVersion; + } +} diff --git a/src/TalisOrm/ChildEntity.php b/src/TalisOrm/ChildEntity.php index 7ded727..ee7527d 100644 --- a/src/TalisOrm/ChildEntity.php +++ b/src/TalisOrm/ChildEntity.php @@ -2,6 +2,9 @@ namespace TalisOrm; +/** + * @see ChildEntityBehavior + */ interface ChildEntity extends Entity { /** diff --git a/src/TalisOrm/ChildEntityBehavior.php b/src/TalisOrm/ChildEntityBehavior.php new file mode 100644 index 0000000..31eb120 --- /dev/null +++ b/src/TalisOrm/ChildEntityBehavior.php @@ -0,0 +1,23 @@ +isNew; + } + + public function markAsPersisted(): void + { + $this->isNew = false; + } +} diff --git a/test/TalisOrm/AggregateRepositoryTest/Line.php b/test/TalisOrm/AggregateRepositoryTest/Line.php index a7d9bfc..77bd998 100644 --- a/test/TalisOrm/AggregateRepositoryTest/Line.php +++ b/test/TalisOrm/AggregateRepositoryTest/Line.php @@ -5,10 +5,13 @@ use Doctrine\DBAL\Schema\Schema; use TalisOrm\AggregateId; use TalisOrm\ChildEntity; +use TalisOrm\ChildEntityBehavior; use TalisOrm\Schema\SpecifiesSchema; final class Line implements ChildEntity, SpecifiesSchema { + use ChildEntityBehavior; + /** * @var OrderId */ @@ -29,11 +32,6 @@ final class Line implements ChildEntity, SpecifiesSchema */ private $quantity; - /** - * @var bool - */ - private $isNew = true; - private function __construct() { } @@ -139,14 +137,4 @@ public static function specifySchema(Schema $schema): void $table->addColumn('quantity', 'integer'); $table->setPrimaryKey(['order_id', 'company_id', 'line_number']); } - - public function isNew(): bool - { - return $this->isNew; - } - - public function markAsPersisted(): void - { - $this->isNew = false; - } } diff --git a/test/TalisOrm/AggregateRepositoryTest/Order.php b/test/TalisOrm/AggregateRepositoryTest/Order.php index 9fffcb6..0437b6a 100644 --- a/test/TalisOrm/AggregateRepositoryTest/Order.php +++ b/test/TalisOrm/AggregateRepositoryTest/Order.php @@ -5,6 +5,7 @@ use DateTimeImmutable; use Doctrine\DBAL\Schema\Schema; use TalisOrm\Aggregate; +use TalisOrm\AggregateBehavior; use TalisOrm\AggregateId; use TalisOrm\ChildEntity; use TalisOrm\DateTimeUtil; @@ -14,7 +15,7 @@ final class Order implements Aggregate, SpecifiesSchema { - use EventRecordingCapabilities; + use AggregateBehavior; /** * @var OrderId @@ -31,21 +32,6 @@ final class Order implements Aggregate, SpecifiesSchema */ private $lines = []; - /** - * @var array - */ - private $deletedChildEntities = []; - - /** - * @var bool - */ - private $isNew = true; - - /** - * @var int - */ - private $aggregateVersion; - private function __construct() { } @@ -201,20 +187,6 @@ public static function identifierForQuery(AggregateId $aggregateId): array ]; } - public function deletedChildEntities(): array - { - $deletedChildEntities = $this->deletedChildEntities; - - $this->deletedChildEntities = []; - - return $deletedChildEntities; - } - - private function deleteChildEntity(ChildEntity $childEntity) - { - $this->deletedChildEntities[] = $childEntity; - } - public static function specifySchema(Schema $schema): void { $table = $schema->createTable('orders'); @@ -227,24 +199,6 @@ public static function specifySchema(Schema $schema): void Line::specifySchema($schema); } - public function isNew(): bool - { - return $this->isNew; - } - - public function markAsPersisted(): void - { - $this->isNew = false; - } - - /** - * @return int - */ - public function aggregateVersion() - { - return $this->aggregateVersion; - } - /** * @param int $aggregateVersion * @return void From 0a61a806cc80e8353813f38ad433ceaf6bb8c606 Mon Sep 17 00:00:00 2001 From: Matthias Noback Date: Wed, 15 May 2019 15:57:32 +0200 Subject: [PATCH 2/3] Extract a few traits so we need less boilerplate code --- src/TalisOrm/AggregateRepository.php | 13 ++++++++----- src/TalisOrm/ChildEntity.php | 3 ++- test/TalisOrm/AggregateRepositoryTest/Line.php | 2 +- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/TalisOrm/AggregateRepository.php b/src/TalisOrm/AggregateRepository.php index e5651a5..71727cd 100644 --- a/src/TalisOrm/AggregateRepository.php +++ b/src/TalisOrm/AggregateRepository.php @@ -75,7 +75,7 @@ public function getById(string $aggregateClass, AggregateId $aggregateId): Aggre $aggregateState = $this->getAggregateState($aggregateClass, $aggregateId); - $childEntitiesByType = $this->getChildEntitiesByType($aggregateClass, $aggregateId); + $childEntitiesByType = $this->getChildEntitiesByType($aggregateClass, $aggregateId, $aggregateState); $aggregate = $aggregateClass::fromState($aggregateState, $childEntitiesByType); @@ -116,8 +116,11 @@ private function getAggregateState(string $aggregateClass, AggregateId $aggregat /** * @return array[] */ - private function getChildEntitiesByType(string $aggregateClass, AggregateId $aggregateId): array - { + private function getChildEntitiesByType( + string $aggregateClass, + AggregateId $aggregateId, + array $aggregateState + ): array { $childEntitiesByType = []; foreach ($aggregateClass::childEntityTypes() as $childEntityType) { @@ -127,8 +130,8 @@ private function getChildEntitiesByType(string $aggregateClass, AggregateId $agg ); $childEntitiesByType[$childEntityType] = array_map( - function (array $childEntityState) use ($childEntityType) { - $childEntity = $childEntityType::fromState($childEntityState); + function (array $childEntityState) use ($childEntityType, $aggregateState) { + $childEntity = $childEntityType::fromState($childEntityState, $aggregateState); $childEntity->markAsPersisted(); diff --git a/src/TalisOrm/ChildEntity.php b/src/TalisOrm/ChildEntity.php index ee7527d..0feae5e 100644 --- a/src/TalisOrm/ChildEntity.php +++ b/src/TalisOrm/ChildEntity.php @@ -19,7 +19,8 @@ interface ChildEntity extends Entity * return $line; * * @param array $state + * @param array $aggregateState * @return static */ - public static function fromState(array $state); + public static function fromState(array $state, array $aggregateState); } diff --git a/test/TalisOrm/AggregateRepositoryTest/Line.php b/test/TalisOrm/AggregateRepositoryTest/Line.php index 77bd998..00af267 100644 --- a/test/TalisOrm/AggregateRepositoryTest/Line.php +++ b/test/TalisOrm/AggregateRepositoryTest/Line.php @@ -89,7 +89,7 @@ public function state(): array ]; } - public static function fromState(array $state): Line + public static function fromState(array $state, array $aggregateState): Line { $line = new self(); From 522da05f30a8d9692d0be8b4a82e1bc33359d0b3 Mon Sep 17 00:00:00 2001 From: Matthias Noback Date: Wed, 15 May 2019 16:14:04 +0200 Subject: [PATCH 3/3] Allow passing in extra state from the custom repository class This may be needed in case the tables for aggregate root and child entities don't containa ll the information you may need inside the entity. This might be considered an issue with the database schema, but you may not be able to fix the problem. So instead, you need a work-around, which is provided by this change. --- src/TalisOrm/AggregateRepository.php | 3 +- .../AbstractAggregateRepositoryTest.php | 78 +++++++++++++++---- .../TalisOrm/AggregateRepositoryTest/Line.php | 15 +++- .../AggregateRepositoryTest/Order.php | 17 +++- .../OrderTalisOrmRepository.php | 4 +- 5 files changed, 98 insertions(+), 19 deletions(-) diff --git a/src/TalisOrm/AggregateRepository.php b/src/TalisOrm/AggregateRepository.php index 71727cd..752f0c5 100644 --- a/src/TalisOrm/AggregateRepository.php +++ b/src/TalisOrm/AggregateRepository.php @@ -63,7 +63,7 @@ public function save(Aggregate $aggregate): void $this->eventDispatcher->dispatch($aggregate->releaseEvents()); } - public function getById(string $aggregateClass, AggregateId $aggregateId): Aggregate + public function getById(string $aggregateClass, AggregateId $aggregateId, array $extraState = []): Aggregate { if (!is_a($aggregateClass, Aggregate::class, true)) { throw new InvalidArgumentException(sprintf( @@ -74,6 +74,7 @@ public function getById(string $aggregateClass, AggregateId $aggregateId): Aggre } $aggregateState = $this->getAggregateState($aggregateClass, $aggregateId); + $aggregateState = array_merge($aggregateState, $extraState); $childEntitiesByType = $this->getChildEntitiesByType($aggregateClass, $aggregateId, $aggregateState); diff --git a/test/TalisOrm/AbstractAggregateRepositoryTest.php b/test/TalisOrm/AbstractAggregateRepositoryTest.php index ad8e1f7..19df21b 100644 --- a/test/TalisOrm/AbstractAggregateRepositoryTest.php +++ b/test/TalisOrm/AbstractAggregateRepositoryTest.php @@ -71,7 +71,8 @@ public function it_saves_an_aggregate() { $aggregate = Order::create( new OrderId('91338a57-5c9a-40e8-b5e8-803e8175c7d7', 5), - DateTimeUtil::createDateTimeImmutable('2018-10-03') + DateTimeUtil::createDateTimeImmutable('2018-10-03'), + $this->quantityPrecision() ); $this->repository->save($aggregate); @@ -81,6 +82,37 @@ public function it_saves_an_aggregate() self::assertEquals([new OrderCreated()], $this->eventDispatcher->dispatchedEvents()); } + /** + * @test + */ + public function when_loading_an_aggregate_it_can_inject_some_extra_state(): void + { + $aggregate = Order::create( + new OrderId('91338a57-5c9a-40e8-b5e8-803e8175c7d7', 5), + DateTimeUtil::createDateTimeImmutable('2019-05-15'), + $this->quantityPrecision() + ); + $aggregate->addLine( + new LineNumber(1), + new ProductId('73d46c97-a71b-4e3c-9633-bb7a8603b301', 5), + new Quantity(10) + ); + $this->repository->save($aggregate); + + $fromDatabase = $this->repository->getById($aggregate->orderId()); + + /* + * The quantity precision doesn't come from the database, but from the array of extra state, provided by the + * `OrderTalisOrmRepository`. + */ + self::assertSame(2, $fromDatabase->quantityPrecision()); + + /* + * It's also available to the `fromState()` method of the `Line` child entity. + */ + self::assertSame(2, $fromDatabase->lines()[0]->quantityPrecision()); + } + /** * @test */ @@ -88,7 +120,8 @@ public function it_updates_an_aggregate() { $aggregate = Order::create( new OrderId('91338a57-5c9a-40e8-b5e8-803e8175c7d7', 5), - DateTimeUtil::createDateTimeImmutable('2018-10-03') + DateTimeUtil::createDateTimeImmutable('2018-10-03'), + $this->quantityPrecision() ); $this->repository->save($aggregate); @@ -114,13 +147,15 @@ public function it_triggers_a_unique_constraint_exception_if_an_id_is_reused_for { $aggregate = Order::create( new OrderId('91338a57-5c9a-40e8-b5e8-803e8175c7d7', 5), - DateTimeUtil::createDateTimeImmutable('2018-10-03') + DateTimeUtil::createDateTimeImmutable('2018-10-03'), + $this->quantityPrecision() ); $this->repository->save($aggregate); $aggregateWithSameId = Order::create( new OrderId('91338a57-5c9a-40e8-b5e8-803e8175c7d7', 5), - DateTimeUtil::createDateTimeImmutable('2018-10-04') + DateTimeUtil::createDateTimeImmutable('2018-10-04'), + $this->quantityPrecision() ); $this->expectException(UniqueConstraintViolationException::class); @@ -135,7 +170,8 @@ public function it_guards_against_concurrent_updates() { $aggregate = Order::create( new OrderId('91338a57-5c9a-40e8-b5e8-803e8175c7d7', 5), - DateTimeUtil::createDateTimeImmutable('2018-10-03') + DateTimeUtil::createDateTimeImmutable('2018-10-03'), + $this->quantityPrecision() ); $this->repository->save($aggregate); @@ -166,7 +202,8 @@ public function it_guards_against_concurrent_updates_if_the_aggregate_has_a_lowe { $aggregate = Order::create( new OrderId('91338a57-5c9a-40e8-b5e8-803e8175c7d7', 5), - DateTimeUtil::createDateTimeImmutable('2018-10-03') + DateTimeUtil::createDateTimeImmutable('2018-10-03'), + $this->quantityPrecision() ); $this->repository->save($aggregate); @@ -194,7 +231,8 @@ public function it_saves_an_aggregate_with_its_child_entities() { $aggregate = Order::create( new OrderId('91338a57-5c9a-40e8-b5e8-803e8175c7d7', 5), - DateTimeUtil::createDateTimeImmutable('2018-10-03') + DateTimeUtil::createDateTimeImmutable('2018-10-03'), + $this->quantityPrecision() ); $aggregate->addLine( new LineNumber(1), @@ -222,7 +260,8 @@ public function entities_will_only_be_marked_as_persisted_if_all_queries_within_ { $aggregate = Order::create( new OrderId('91338a57-5c9a-40e8-b5e8-803e8175c7d7', 5), - DateTimeUtil::createDateTimeImmutable('2018-10-03') + DateTimeUtil::createDateTimeImmutable('2018-10-03'), + $this->quantityPrecision() ); $aggregate->addLine( new LineNumber(1), @@ -255,7 +294,8 @@ public function it_creates_multiple_child_entities_in_the_database() { $aggregate = Order::create( new OrderId('91338a57-5c9a-40e8-b5e8-803e8175c7d7', 5), - DateTimeUtil::createDateTimeImmutable('2018-10-03') + DateTimeUtil::createDateTimeImmutable('2018-10-03'), + $this->quantityPrecision() ); $aggregate->addLine( new LineNumber(1), @@ -289,7 +329,8 @@ public function it_updates_multiple_child_entities_in_the_database() { $aggregate = Order::create( new OrderId('91338a57-5c9a-40e8-b5e8-803e8175c7d7', 5), - DateTimeUtil::createDateTimeImmutable('2018-10-03') + DateTimeUtil::createDateTimeImmutable('2018-10-03'), + $this->quantityPrecision() ); $aggregate->addLine( new LineNumber(1), @@ -331,7 +372,8 @@ public function it_deletes_child_entities_that_have_been_removed_from_the_aggreg { $aggregate = Order::create( new OrderId('91338a57-5c9a-40e8-b5e8-803e8175c7d7', 5), - DateTimeUtil::createDateTimeImmutable('2018-10-03') + DateTimeUtil::createDateTimeImmutable('2018-10-03'), + $this->quantityPrecision() ); $aggregate->addLine( new LineNumber(1), @@ -369,7 +411,8 @@ public function it_deletes_an_aggregate_with_its_child_entities() { $aggregate = Order::create( new OrderId('91338a57-5c9a-40e8-b5e8-803e8175c7d7', 5), - DateTimeUtil::createDateTimeImmutable('2018-10-03') + DateTimeUtil::createDateTimeImmutable('2018-10-03'), + $this->quantityPrecision() ); $aggregate->addLine( new LineNumber(1), @@ -391,12 +434,14 @@ public function it_does_not_delete_all_aggregates_of_the_same_type() { $aggregate1 = Order::create( new OrderId('91338a57-5c9a-40e8-b5e8-803e8175c7d7', 5), - DateTimeUtil::createDateTimeImmutable('2018-10-03') + DateTimeUtil::createDateTimeImmutable('2018-10-03'), + $this->quantityPrecision() ); $this->repository->save($aggregate1); $aggregate2 = Order::create( new OrderId('c8ee1ee6-7757-4661-81fb-5b327badbff8', 5), - DateTimeUtil::createDateTimeImmutable('2018-10-04') + DateTimeUtil::createDateTimeImmutable('2018-10-04'), + $this->quantityPrecision() ); $this->repository->save($aggregate2); @@ -409,4 +454,9 @@ public function it_does_not_delete_all_aggregates_of_the_same_type() $this->expectException(AggregateNotFoundException::class); $this->repository->getById($aggregate1->orderId()); } + + private function quantityPrecision(): int + { + return 2; + } } diff --git a/test/TalisOrm/AggregateRepositoryTest/Line.php b/test/TalisOrm/AggregateRepositoryTest/Line.php index 00af267..10337ab 100644 --- a/test/TalisOrm/AggregateRepositoryTest/Line.php +++ b/test/TalisOrm/AggregateRepositoryTest/Line.php @@ -32,6 +32,11 @@ final class Line implements ChildEntity, SpecifiesSchema */ private $quantity; + /** + * @var int + */ + private $quantityPrecision; + private function __construct() { } @@ -40,7 +45,8 @@ public static function create( OrderId $orderId, LineNumber $lineNumber, ProductId $productId, - Quantity $quantity + Quantity $quantity, + int $quantityPrecision ): Line { $line = new self(); @@ -48,6 +54,7 @@ public static function create( $line->lineNumber = $lineNumber; $line->productId = $productId; $line->quantity = $quantity; + $line->quantityPrecision = $quantityPrecision; return $line; } @@ -78,6 +85,11 @@ public function quantity(): Quantity return $this->quantity; } + public function quantityPrecision(): int + { + return $this->quantityPrecision; + } + public function state(): array { return [ @@ -97,6 +109,7 @@ public static function fromState(array $state, array $aggregateState): Line $line->lineNumber = new LineNumber((int)$state['line_number']); $line->productId = new ProductId($state['product_id'], (int)$state['company_id']); $line->quantity = new Quantity((int)$state['quantity']); + $line->quantityPrecision = $aggregateState['quantityPrecision']; return $line; } diff --git a/test/TalisOrm/AggregateRepositoryTest/Order.php b/test/TalisOrm/AggregateRepositoryTest/Order.php index 0437b6a..9423fe8 100644 --- a/test/TalisOrm/AggregateRepositoryTest/Order.php +++ b/test/TalisOrm/AggregateRepositoryTest/Order.php @@ -32,6 +32,11 @@ final class Order implements Aggregate, SpecifiesSchema */ private $lines = []; + /** + * @var int + */ + private $quantityPrecision; + private function __construct() { } @@ -41,12 +46,13 @@ private function __construct() * @param DateTimeImmutable $orderDate * @return Order */ - public static function create(OrderId $orderId, DateTimeImmutable $orderDate) + public static function create(OrderId $orderId, DateTimeImmutable $orderDate, int $quantityPrecision) { $order = new self(); $order->orderId = $orderId; $order->orderDate = $orderDate; + $order->quantityPrecision = $quantityPrecision; $order->recordThat(new OrderCreated()); @@ -72,7 +78,7 @@ public function update(DateTimeImmutable $orderDate) */ public function addLine(LineNumber $lineId, ProductId $productId, Quantity $quantity) { - $this->lines[] = Line::create($this->orderId, $lineId, $productId, $quantity); + $this->lines[] = Line::create($this->orderId, $lineId, $productId, $quantity, $this->quantityPrecision); $this->recordThat(new LineAdded()); } @@ -160,6 +166,8 @@ public static function fromState(array $aggregateState, array $childEntitiesByTy $order->aggregateVersion = (int)$aggregateState[Aggregate::VERSION_COLUMN]; + $order->quantityPrecision = $aggregateState['quantityPrecision']; + return $order; } @@ -216,4 +224,9 @@ public function lines(): array { return $this->lines; } + + public function quantityPrecision(): int + { + return $this->quantityPrecision; + } } diff --git a/test/TalisOrm/AggregateRepositoryTest/OrderTalisOrmRepository.php b/test/TalisOrm/AggregateRepositoryTest/OrderTalisOrmRepository.php index 0e439cc..15e4c2f 100644 --- a/test/TalisOrm/AggregateRepositoryTest/OrderTalisOrmRepository.php +++ b/test/TalisOrm/AggregateRepositoryTest/OrderTalisOrmRepository.php @@ -29,7 +29,9 @@ public function save(Order $order) public function getById(OrderId $orderId) { - return $this->aggregateRepository->getById(Order::class, $orderId); + return $this->aggregateRepository->getById(Order::class, $orderId, [ + 'quantityPrecision' => 2 + ]); } public function delete(Order $order)