diff --git a/src/Clock.php b/src/Clock.php deleted file mode 100644 index d438790c..00000000 --- a/src/Clock.php +++ /dev/null @@ -1,44 +0,0 @@ -modify(sprintf('+%s seconds', $seconds)); - - return; - } - - sleep($seconds); - } - - public static function createDateTimeImmutable(): DateTimeImmutable - { - return self::$frozenDateTime ?: new DateTimeImmutable(); - } - - public static function reset(): void - { - self::$frozenDateTime = null; - } -} diff --git a/src/Clock/Clock.php b/src/Clock/Clock.php new file mode 100644 index 00000000..7be4fad8 --- /dev/null +++ b/src/Clock/Clock.php @@ -0,0 +1,12 @@ +frozenDateTime = $frozenDateTime; + } + + /** + * @param positive-int $seconds + */ + public function sleep(int $seconds): void + { + $this->frozenDateTime = $this->frozenDateTime->modify(sprintf('+%s seconds', $seconds)); + } + + public function create(): DateTimeImmutable + { + return $this->frozenDateTime; + } +} diff --git a/src/Clock/SystemClock.php b/src/Clock/SystemClock.php new file mode 100644 index 00000000..08d2c154 --- /dev/null +++ b/src/Clock/SystemClock.php @@ -0,0 +1,15 @@ +withRecordedOn($this->clock->create()); + } +} diff --git a/src/Repository/DefaultRepository.php b/src/Repository/DefaultRepository.php index 977ac911..ba2e2109 100644 --- a/src/Repository/DefaultRepository.php +++ b/src/Repository/DefaultRepository.php @@ -5,8 +5,9 @@ namespace Patchlevel\EventSourcing\Repository; use Patchlevel\EventSourcing\Aggregate\AggregateRoot; -use Patchlevel\EventSourcing\Clock; +use Patchlevel\EventSourcing\Clock\SystemClock; use Patchlevel\EventSourcing\EventBus\Decorator\MessageDecorator; +use Patchlevel\EventSourcing\EventBus\Decorator\RecordedOnDecorator; use Patchlevel\EventSourcing\EventBus\EventBus; use Patchlevel\EventSourcing\EventBus\Message; use Patchlevel\EventSourcing\Metadata\AggregateRoot\AggregateRootMetadata; @@ -37,7 +38,7 @@ final class DefaultRepository implements Repository private ?SnapshotStore $snapshotStore; private LoggerInterface $logger; private AggregateRootMetadata $metadata; - private ?MessageDecorator $messageDecorator; + private MessageDecorator $messageDecorator; /** * @param class-string $aggregateClass @@ -54,7 +55,7 @@ public function __construct( $this->eventBus = $eventBus; $this->aggregateClass = $aggregateClass; $this->snapshotStore = $snapshotStore; - $this->messageDecorator = $messageDecorator; + $this->messageDecorator = $messageDecorator ?? new RecordedOnDecorator(new SystemClock()); $this->logger = $logger ?? new NullLogger(); $this->metadata = $aggregateClass::metadata(); } @@ -128,14 +129,9 @@ static function (object $event) use ($aggregate, &$playhead, $messageDecorator) $message = Message::create($event) ->withAggregateClass($aggregate::class) ->withAggregateId($aggregate->aggregateRootId()) - ->withPlayhead(++$playhead) - ->withRecordedOn(Clock::createDateTimeImmutable()); + ->withPlayhead(++$playhead); - if ($messageDecorator) { - $message = $messageDecorator($message); - } - - return $message; + return $messageDecorator($message); }, $events ); diff --git a/src/Repository/DefaultRepositoryManager.php b/src/Repository/DefaultRepositoryManager.php index d49581a7..a9663e76 100644 --- a/src/Repository/DefaultRepositoryManager.php +++ b/src/Repository/DefaultRepositoryManager.php @@ -5,7 +5,9 @@ namespace Patchlevel\EventSourcing\Repository; use Patchlevel\EventSourcing\Aggregate\AggregateRoot; +use Patchlevel\EventSourcing\Clock\SystemClock; use Patchlevel\EventSourcing\EventBus\Decorator\MessageDecorator; +use Patchlevel\EventSourcing\EventBus\Decorator\RecordedOnDecorator; use Patchlevel\EventSourcing\EventBus\EventBus; use Patchlevel\EventSourcing\Metadata\AggregateRoot\AggregateRootClassNotRegistered; use Patchlevel\EventSourcing\Metadata\AggregateRoot\AggregateRootRegistry; @@ -22,7 +24,7 @@ final class DefaultRepositoryManager implements RepositoryManager private Store $store; private EventBus $eventBus; private ?SnapshotStore $snapshotStore; - private ?MessageDecorator $messageDecorator; + private MessageDecorator $messageDecorator; private LoggerInterface $logger; /** @var array, Repository> */ @@ -40,7 +42,7 @@ public function __construct( $this->store = $store; $this->eventBus = $eventBus; $this->snapshotStore = $snapshotStore; - $this->messageDecorator = $messageDecorator; + $this->messageDecorator = $messageDecorator ?? new RecordedOnDecorator(new SystemClock()); $this->logger = $logger ?? new NullLogger(); } diff --git a/tests/Integration/BasicImplementation/BasicIntegrationTest.php b/tests/Integration/BasicImplementation/BasicIntegrationTest.php index f3cfedb7..399ffca5 100644 --- a/tests/Integration/BasicImplementation/BasicIntegrationTest.php +++ b/tests/Integration/BasicImplementation/BasicIntegrationTest.php @@ -5,12 +5,16 @@ namespace Patchlevel\EventSourcing\Tests\Integration\BasicImplementation; use Doctrine\DBAL\Connection; +use Patchlevel\EventSourcing\Clock\SystemClock; +use Patchlevel\EventSourcing\EventBus\Decorator\ChainMessageDecorator; +use Patchlevel\EventSourcing\EventBus\Decorator\RecordedOnDecorator; use Patchlevel\EventSourcing\EventBus\DefaultEventBus; use Patchlevel\EventSourcing\EventBus\SymfonyEventBus; +use Patchlevel\EventSourcing\Metadata\AggregateRoot\AggregateRootRegistry; use Patchlevel\EventSourcing\Metadata\AggregateRoot\AttributeAggregateRootRegistryFactory; use Patchlevel\EventSourcing\Projection\MetadataAwareProjectionHandler; use Patchlevel\EventSourcing\Projection\ProjectionListener; -use Patchlevel\EventSourcing\Repository\DefaultRepository; +use Patchlevel\EventSourcing\Repository\DefaultRepositoryManager; use Patchlevel\EventSourcing\Schema\DoctrineSchemaManager; use Patchlevel\EventSourcing\Serializer\DefaultEventSerializer; use Patchlevel\EventSourcing\Snapshot\Adapter\InMemorySnapshotAdapter; @@ -60,13 +64,14 @@ public function testSuccessful(): void 'eventstore' ); - $repository = new DefaultRepository( + $manager = new DefaultRepositoryManager( + new AggregateRootRegistry(['profile' => Profile::class]), $store, $eventStream, - Profile::class, null, - new FooMessageDecorator() + new ChainMessageDecorator([new RecordedOnDecorator(new SystemClock()), new FooMessageDecorator()]) ); + $repository = $manager->get(Profile::class); // create tables $profileProjection->create(); @@ -82,7 +87,14 @@ public function testSuccessful(): void self::assertSame('1', $result['id']); self::assertSame('John', $result['name']); - $repository = new DefaultRepository($store, $eventStream, Profile::class); + $manager = new DefaultRepositoryManager( + new AggregateRootRegistry(['profile' => Profile::class]), + $store, + $eventStream, + null, + new ChainMessageDecorator([new RecordedOnDecorator(new SystemClock())]) + ); + $repository = $manager->get(Profile::class); $profile = $repository->load('1'); self::assertInstanceOf(Profile::class, $profile); @@ -111,7 +123,14 @@ public function testWithSymfonySuccessful(): void 'eventstore' ); - $repository = new DefaultRepository($store, $eventStream, Profile::class); + $manager = new DefaultRepositoryManager( + new AggregateRootRegistry(['profile' => Profile::class]), + $store, + $eventStream, + null, + new ChainMessageDecorator([new RecordedOnDecorator(new SystemClock())]) + ); + $repository = $manager->get(Profile::class); // create tables $profileProjection->create(); @@ -127,13 +146,14 @@ public function testWithSymfonySuccessful(): void self::assertSame('1', $result['id']); self::assertSame('John', $result['name']); - $repository = new DefaultRepository( + $manager = new DefaultRepositoryManager( + new AggregateRootRegistry(['profile' => Profile::class]), $store, $eventStream, - Profile::class, null, - new FooMessageDecorator() + new ChainMessageDecorator([new RecordedOnDecorator(new SystemClock()), new FooMessageDecorator()]) ); + $repository = $manager->get(Profile::class); $profile = $repository->load('1'); @@ -161,13 +181,14 @@ public function testMultiTableSuccessful(): void (new AttributeAggregateRootRegistryFactory())->create([__DIR__ . '/Aggregate']), ); - $repository = new DefaultRepository( + $manager = new DefaultRepositoryManager( + new AggregateRootRegistry(['profile' => Profile::class]), $store, $eventStream, - Profile::class, null, - new FooMessageDecorator() + new ChainMessageDecorator([new RecordedOnDecorator(new SystemClock()), new FooMessageDecorator()]) ); + $repository = $manager->get(Profile::class); // create tables $profileProjection->create(); @@ -183,7 +204,14 @@ public function testMultiTableSuccessful(): void self::assertSame('1', $result['id']); self::assertSame('John', $result['name']); - $repository = new DefaultRepository($store, $eventStream, Profile::class); + $manager = new DefaultRepositoryManager( + new AggregateRootRegistry(['profile' => Profile::class]), + $store, + $eventStream, + null, + new ChainMessageDecorator([new RecordedOnDecorator(new SystemClock())]) + ); + $repository = $manager->get(Profile::class); $profile = $repository->load('1'); self::assertInstanceOf(Profile::class, $profile); @@ -211,15 +239,14 @@ public function testSnapshot(): void 'eventstore' ); - $snapshotStore = new DefaultSnapshotStore(['default' => new InMemorySnapshotAdapter()]); - - $repository = new DefaultRepository( + $manager = new DefaultRepositoryManager( + new AggregateRootRegistry(['profile' => Profile::class]), $store, $eventStream, - Profile::class, - $snapshotStore, - new FooMessageDecorator() + new DefaultSnapshotStore(['default' => new InMemorySnapshotAdapter()]), + new ChainMessageDecorator([new RecordedOnDecorator(new SystemClock()), new FooMessageDecorator()]) ); + $repository = $manager->get(Profile::class); // create tables $profileProjection->create(); @@ -235,7 +262,14 @@ public function testSnapshot(): void self::assertSame('1', $result['id']); self::assertSame('John', $result['name']); - $repository = new DefaultRepository($store, $eventStream, Profile::class, $snapshotStore); + $manager = new DefaultRepositoryManager( + new AggregateRootRegistry(['profile' => Profile::class]), + $store, + $eventStream, + null, + new ChainMessageDecorator([new RecordedOnDecorator(new SystemClock())]) + ); + $repository = $manager->get(Profile::class); $profile = $repository->load('1'); self::assertInstanceOf(Profile::class, $profile); diff --git a/tests/Unit/Clock/FreezeClockTest.php b/tests/Unit/Clock/FreezeClockTest.php new file mode 100644 index 00000000..12f40325 --- /dev/null +++ b/tests/Unit/Clock/FreezeClockTest.php @@ -0,0 +1,50 @@ +create(); + + self::assertSame($current, $new); + } + + public function testSleep(): void + { + $date1 = new DateTimeImmutable(); + $clock = new FreezeClock($date1); + $clock->sleep(1); + $date2 = $clock->create(); + + $diff = $date1->diff($date2); + + self::assertSame(1, $diff->s); + } + + public function testReFreeze(): void + { + $date1 = new DateTimeImmutable(); + $clock = new FreezeClock($date1); + $new1 = $clock->create(); + + $date2 = new DateTimeImmutable(); + $clock->update($date2); + $new2 = $clock->create(); + + self::assertSame($date1, $new1); + self::assertSame($date2, $new2); + self::assertNotSame($new1, $new2); + } +} diff --git a/tests/Unit/Clock/SystemClockTest.php b/tests/Unit/Clock/SystemClockTest.php new file mode 100644 index 00000000..f935c642 --- /dev/null +++ b/tests/Unit/Clock/SystemClockTest.php @@ -0,0 +1,23 @@ +create(); + $after = new DateTimeImmutable(); + + self::assertGreaterThanOrEqual($before, $date); + self::assertLessThanOrEqual($after, $date); + } +} diff --git a/tests/Unit/ClockTest.php b/tests/Unit/ClockTest.php deleted file mode 100644 index ad8f57da..00000000 --- a/tests/Unit/ClockTest.php +++ /dev/null @@ -1,84 +0,0 @@ -diff($date2); - - self::assertSame(1, $diff->s); - } - - public function testSleepWithFrozenClock(): void - { - $current = new DateTimeImmutable(); - Clock::freeze($current); - - $date1 = Clock::createDateTimeImmutable(); - Clock::sleep(45); - $date2 = Clock::createDateTimeImmutable(); - - $diff = $date1->diff($date2); - - self::assertSame(45, $diff->s); - } -} diff --git a/tests/Unit/EventBus/MessageTest.php b/tests/Unit/EventBus/MessageTest.php index 05285c5c..ea2a5436 100644 --- a/tests/Unit/EventBus/MessageTest.php +++ b/tests/Unit/EventBus/MessageTest.php @@ -5,7 +5,6 @@ namespace Patchlevel\EventSourcing\Tests\Unit\EventBus; use DateTimeImmutable; -use Patchlevel\EventSourcing\Clock; use Patchlevel\EventSourcing\EventBus\HeaderNotFound; use Patchlevel\EventSourcing\EventBus\Message; use Patchlevel\EventSourcing\Tests\Unit\Fixture\Email; @@ -17,17 +16,8 @@ /** @covers \Patchlevel\EventSourcing\EventBus\Message */ class MessageTest extends TestCase { - public function tearDown(): void - { - Clock::reset(); - } - public function testEmptyMessage(): void { - $recordedAt = new DateTimeImmutable('2020-05-06 13:34:24'); - - Clock::freeze($recordedAt); - $id = ProfileId::fromString('1'); $email = Email::fromString('hallo@patchlevel.de'); @@ -47,8 +37,6 @@ public function testCreateMessageWithHeader(): void { $recordedAt = new DateTimeImmutable('2020-05-06 13:34:24'); - Clock::freeze($recordedAt); - $id = ProfileId::fromString('1'); $email = Email::fromString('hallo@patchlevel.de'); @@ -74,8 +62,6 @@ public function testChangeHeader(): void { $recordedAt = new DateTimeImmutable('2020-05-06 13:34:24'); - Clock::freeze($recordedAt); - $id = ProfileId::fromString('1'); $email = Email::fromString('hallo@patchlevel.de'); @@ -120,8 +106,6 @@ public function testCustomHeaders(): void { $recordedAt = new DateTimeImmutable('2020-05-06 13:34:24'); - Clock::freeze($recordedAt); - $id = ProfileId::fromString('1'); $email = Email::fromString('hallo@patchlevel.de');