/** * @return MigrationsService */ protected function createServiceWithMigrations() { $this->migrationsDirectory = __DIR__ . '/../../../Fixtures/Migrations'; require_once __DIR__ . '/../../../Fixtures/BlogEventSourced.php'; // simulate an application and aggregate version state $currentApplicationVersion = Version::fromString('0.1.0'); VersionManager::setCurrentApplicationVersion($currentApplicationVersion); VersionManager::persistVersionOfClass(PostEventSourced::class, $currentApplicationVersion); VersionManager::persistVersionOfClass(\BlogEventSourced::class, $currentApplicationVersion); // creating migration store $aggregates = [PostEventSourced::class, \BlogEventSourced::class]; $migratorStore = new InMemoryMigrationStore(); $migratorStore->persist(new Migration($aggregates, $currentApplicationVersion, new \DateTime())); // creating event store $eventStore = new InMemoryEventStore(); // add the event store flow $postId1 = PostId::fromNative(md5(rand())); $postEventStream1 = new EventStream($this->streamName(PostEventSourced::class), $postId1, [new PostWasCreated($postId1, 'Best restaurants in barcelona', 'empty'), new PostTitleWasChanged($postId1, 'Best cuban restaurants in barcelona')]); $postId2 = PostId::fromNative(md5(rand())); $postEventStream2 = new EventStream($this->streamName(PostEventSourced::class), $postId2, [new PostWasCreated($postId2, 'Best things to do with children in barcelona', 'empty'), new PostTitleWasChanged($postId2, 'Things to do with children in barcelona this weekend')]); $eventStore->persist($postEventStream1, $currentApplicationVersion, $currentApplicationVersion); $eventStore->persist($postEventStream2, $currentApplicationVersion, $currentApplicationVersion); // fake BlogEventSourced event stream $postId2 = PostId::fromNative(md5(rand())); $postEventStream2 = new EventStream($this->streamName(\BlogEventSourced::class), $postId2, [new PostWasCreated($postId2, 'Blog', 'empty'), new PostTitleWasChanged($postId2, 'new title')]); $eventStore->persist($postEventStream2, $currentApplicationVersion, $currentApplicationVersion); return new MigrationsService(new Migrator($this->getClassMetadataFactory(), $migratorStore, $eventStore, $this->migrationsDirectory)); }
/** * Test Generate method. */ public function testGenerate() { require_once __DIR__ . '/../../../Fixtures/BlogEventSourced.php'; $this->given($generator = $this->createGenerator())->and($aggregateClass = PostEventSourced::class)->and($version = VersionManager::versionOfClass($aggregateClass))->when($generator->generate($aggregateClass, $version))->then()->boolean(file_exists($this->getMigratorFileName($aggregateClass, $version)))->isTrue()->and()->exception(function () use($generator, $aggregateClass, $version) { $generator->generate($aggregateClass, $version); })->isInstanceOf(\RuntimeException::class); $this->given($generator = $this->createGenerator())->and($aggregateClass = \BlogEventSourced::class)->and($version = VersionManager::versionOfClass($aggregateClass))->when($generator->generate($aggregateClass, $version))->then()->boolean(file_exists($this->getMigratorFileName($aggregateClass, $version)))->isTrue()->boolean($generator->existsDirectory($version))->isTrue(); }
/** * Test migrate method. */ public function testMigrate() { $this->migrationsDirectory = __DIR__ . '/../../Fixtures/Migrations'; require_once __DIR__ . '/../../Fixtures/BlogEventSourced.php'; // simulate an application and aggregate version state $currentApplicationVersion = Version::fromString('0.1.0'); $aggregateVersion = Version::fromString('0.1.0'); VersionManager::setCurrentApplicationVersion($currentApplicationVersion); VersionManager::persistVersionOfClass(PostEventSourced::class, $aggregateVersion); VersionManager::persistVersionOfClass(\BlogEventSourced::class, $aggregateVersion); // creating migration store $aggregates = [PostEventSourced::class, \BlogEventSourced::class]; $migratorStore = new InMemoryMigrationStore(); $migratorStore->persist(new Migration($aggregates, $aggregateVersion, new \DateTime())); // creating event store $eventStore = new InMemoryEventStore(); // add the event store flow $postId1 = PostId::fromNative(md5(rand())); $postEventStream1 = new EventStream($this->streamName(PostEventSourced::class), $postId1, [new PostWasCreated($postId1, 'Best restaurants in barcelona', 'empty'), new PostTitleWasChanged($postId1, 'Best cuban restaurants in barcelona')]); $postId2 = PostId::fromNative(md5(rand())); $postEventStream2 = new EventStream($this->streamName(PostEventSourced::class), $postId2, [new PostWasCreated($postId2, 'Best things to do with children in barcelona', 'empty'), new PostTitleWasChanged($postId2, 'Things to do with children in barcelona this weekend')]); $eventStore->persist($postEventStream1, $aggregateVersion, $currentApplicationVersion); $eventStore->persist($postEventStream2, $aggregateVersion, $currentApplicationVersion); // fake BlogEventSourced event stream $postId3 = PostId::fromNative(md5(rand())); $postEventStream2 = new EventStream($this->streamName(\BlogEventSourced::class), $postId3, []); $eventStore->persist($postEventStream2, $aggregateVersion, $currentApplicationVersion); // creating snapshot store $snapshotStore = new InMemorySnapshotStore(); // creating the migrator $migrator = new MigratorWithSnapshot($this->getClassMetadataFactory(), $migratorStore, $eventStore, $snapshotStore, $this->migrationsDirectory); $emptyMigrator = new MigratorWithSnapshot($this->getClassMetadataFactory(), $migratorStore, $eventStore, $snapshotStore, __DIR__ . '/../../Fixtures/EmptyMigrations'); $this->given($result = $emptyMigrator->migrate())->then()->boolean($result)->isFalse(); $this->given($status = $migrator->status())->and($nextVersion = $status->nextAvailableVersion())->then()->boolean($migratorStore->hasMigration($nextVersion))->isFalse()->variable($snapshotStore->load('post_event_sourced', $postId1, $aggregateVersion, $currentApplicationVersion))->isNull()->and()->when($result = $migrator->migrate())->and(VersionManager::setCurrentApplicationVersion($migratorStore->getLast()->version()))->then()->boolean($migratorStore->hasMigration($nextVersion))->isTrue()->boolean($result)->isTrue()->variable($snapshotStore->load('post_event_sourced', $postId1, $aggregateVersion, $nextVersion))->isNotNull()->variable($snapshotStore->load('post_event_sourced', $postId2, $aggregateVersion, $nextVersion))->isNotNull()->and()->exception(function () use($migrator) { // because the V1_0_0\BlogEventSourcedMigration class return an invalid stream $migrator->migrate(); })->isInstanceOf(\RuntimeException::class); }
/** * Save the aggregate history. * * @param EventSourcedAggregateRootInterface $aggregateRoot */ protected function saveHistory(EventSourcedAggregateRootInterface $aggregateRoot) { $recordedEvents = $aggregateRoot->recordedEvents(); if (count($recordedEvents) > 0) { DomainEventPublisher::publish(new PrePersistEvent($aggregateRoot)); // clear events $aggregateRoot->clearEvents(); // create the eventStream and persist it $applicationVersion = VersionManager::currentApplicationVersion(); $eventStream = new EventStream($this->streamName(), $aggregateRoot->id(), $recordedEvents); $this->eventStore->persist($eventStream, $aggregateRoot->version(), $applicationVersion); DomainEventPublisher::publish(new PostPersistEvent($aggregateRoot)); } }
/** * Load a aggregate snapshot from the storage. * * @param IdInterface $id * * @return Snapshot */ protected function loadSnapshot(IdInterface $id) { $applicationVersion = VersionManager::currentApplicationVersion(); $aggregateVersion = VersionManager::versionOfClass($this->aggregateClassName, $applicationVersion); return $this->snapshotStore->load($this->streamName(), $id, $aggregateVersion, $applicationVersion); }
/** * @return bool */ public function migrate() { $nextMigration = $this->migrationManager()->nextMigrationToExecute(); $currentApplicationVersion = VersionManager::currentApplicationVersion(); if ($nextMigration !== null) { foreach ($nextMigration->aggregates() as $aggregateMigrationClass) { // -- start current application context -- /** @var MigrationInterface $migrationClass */ $migrationClass = new $aggregateMigrationClass(); $aggregateClassName = $migrationClass->aggregateClassName(); $currentAggregateVersion = VersionManager::versionOfClass($aggregateClassName, $currentApplicationVersion); // get all event streams for this aggregate class name $eventStreams = $this->eventStore->loadAll($this->streamName($aggregateClassName), $currentAggregateVersion, $currentApplicationVersion); // -- end current application context -- // -- start new version context -- $nextApplicationVersion = $nextMigration->version(); // iterate for every aggregateRoot event stream foreach ($eventStreams as $aggregateRootEventStream) { // migrate the current aggregate event stream $newAggregateRootEventStream = $migrationClass->migrate($aggregateRootEventStream); if ($newAggregateRootEventStream === null || $newAggregateRootEventStream !== null && !$newAggregateRootEventStream instanceof EventStream) { throw new \RuntimeException(sprintf('Invalid migration class %s. The migration method should return the new EventStream', $aggregateMigrationClass)); } // calculate the new version for every event $pathVersion = 0; foreach ($newAggregateRootEventStream->events() as $event) { $event->setVersion(++$pathVersion); } // and the new version for this aggregateRoot $newAggregateRootVersion = Version::fromString($nextMigration->version()->__toString()); $newAggregateRootVersion->setPatch($pathVersion); // persist the new event stream for this aggregateRoot. $this->migrateAggregateRoot($aggregateClassName, $newAggregateRootEventStream, $newAggregateRootVersion, $nextApplicationVersion); } // persist the new version of this aggregate class in the VersionManager VersionManager::persistVersionOfClass($aggregateClassName, $nextMigration->version(), $nextApplicationVersion); // -- end new version context -- } // persist the new migration in the store $this->migrationManager->persistMigration($nextMigration); return true; } return false; }
/** * @param Snapshot $snapshot * * @return EventSourcedAggregateRootInterface */ protected function snapshotToAggregateRoot(Snapshot $snapshot) { $applicationVersion = VersionManager::currentApplicationVersion(); $history = $this->eventStore->load($this->streamName(), $snapshot->aggregateId(), $snapshot->version(), $applicationVersion); $aggregateRoot = $snapshot->aggregate(); $aggregateRoot->setVersion($snapshot->version()); $aggregateRoot->replay($history); return $aggregateRoot; }
/** * Test setCurrentApplicationVersion method. */ public function testSetCurrentApplicationVersion() { $this->given($applicationVersion = Version::fromString('3.2.0'))->and(VersionManager::setCurrentApplicationVersion($applicationVersion))->then()->object(VersionManager::currentApplicationVersion())->isEqualTo($applicationVersion); }
/** * {@inheritdoc} */ public function version() { if ($this->version === null) { $this->version = VersionManager::versionOf($this); } return $this->version; }