diff --git a/docs/tutorial/bonus_III.md b/docs/tutorial/bonus_III.md new file mode 100644 index 0000000..2a24a97 --- /dev/null +++ b/docs/tutorial/bonus_III.md @@ -0,0 +1 @@ +# Bonus III - Decoupled Functional Core diff --git a/docs/tutorial/part_VII.md b/docs/tutorial/part_VII.md index 61bdfb9..47bbc8f 100644 --- a/docs/tutorial/part_VII.md +++ b/docs/tutorial/part_VII.md @@ -227,7 +227,7 @@ Try to check *John* in again, while keeping an eye on the monitoring app `http:/ ## The End Congratulations! You've mastered the Event Machine tutorial. There are two bonus parts available to learn more -about **custom projections** and **testing with Event Machine**. +about **custom projections**, **testing with Event Machine** and how to achieve a "decoupled functional core". The current implementation is available as a **demo** branch of `proophsoftware/event-machine-skeleton`. There is a second branch called **demo-oop** available that contains a similar implementation, but the `Building` aggregate is designed using an object oriented approach rather than diff --git a/src/Aggregate/ClosureAggregateTranslator.php b/src/Aggregate/ClosureAggregateTranslator.php index f128dfb..61dfe27 100644 --- a/src/Aggregate/ClosureAggregateTranslator.php +++ b/src/Aggregate/ClosureAggregateTranslator.php @@ -37,10 +37,13 @@ final class ClosureAggregateTranslator implements EventStoreAggregateTranslator private $eventApplyMap; - public function __construct(string $aggregateId, array $eventApplyMap) + private $eventClassMap; + + public function __construct(string $aggregateId, array $eventApplyMap, array $eventClassMap) { $this->aggregateId = $aggregateId; $this->eventApplyMap = $eventApplyMap; + $this->eventClassMap = $eventClassMap; } /** @@ -80,8 +83,9 @@ public function reconstituteAggregateFromHistory(AggregateType $aggregateType, I if (null === $this->aggregateReconstructor) { $arId = $this->aggregateId; $eventApplyMap = $this->eventApplyMap; - $this->aggregateReconstructor = function ($historyEvents) use ($arId, $aggregateType, $eventApplyMap) { - return static::reconstituteFromHistory($arId, $aggregateType, $eventApplyMap, $historyEvents); + $eventClassMap = $this->eventClassMap; + $this->aggregateReconstructor = function ($historyEvents) use ($arId, $aggregateType, $eventApplyMap, $eventClassMap) { + return static::reconstituteFromHistory($arId, $aggregateType, $eventApplyMap, $eventClassMap, $historyEvents); }; } diff --git a/src/Aggregate/GenericAggregateRoot.php b/src/Aggregate/GenericAggregateRoot.php index 004ac4c..6105958 100644 --- a/src/Aggregate/GenericAggregateRoot.php +++ b/src/Aggregate/GenericAggregateRoot.php @@ -11,7 +11,7 @@ namespace Prooph\EventMachine\Aggregate; -use Prooph\EventMachine\Eventing\GenericJsonSchemaEvent; +use Prooph\EventMachine\Messaging\GenericJsonSchemaEvent; use Prooph\EventSourcing\Aggregate\AggregateType; use Prooph\EventSourcing\Aggregate\AggregateTypeProvider; use Prooph\EventSourcing\Aggregate\Exception\RuntimeException; @@ -36,6 +36,11 @@ final class GenericAggregateRoot implements AggregateTypeProvider */ private $eventApplyMap; + /** + * @var string[] + */ + private $eventClassMap; + /** * @var mixed */ @@ -58,19 +63,20 @@ final class GenericAggregateRoot implements AggregateTypeProvider /** * @throws RuntimeException */ - protected static function reconstituteFromHistory(string $aggregateId, AggregateType $aggregateType, array $eventApplyMap, \Iterator $historyEvents): self + protected static function reconstituteFromHistory(string $aggregateId, AggregateType $aggregateType, array $eventApplyMap, array $eventClassMap, \Iterator $historyEvents): self { - $instance = new self($aggregateId, $aggregateType, $eventApplyMap); + $instance = new self($aggregateId, $aggregateType, $eventApplyMap, $eventClassMap); $instance->replay($historyEvents); return $instance; } - public function __construct(string $aggregateId, AggregateType $aggregateType, array $eventApplyMap) + public function __construct(string $aggregateId, AggregateType $aggregateType, array $eventApplyMap, array $eventClassMap) { $this->aggregateId = $aggregateId; $this->aggregateType = $aggregateType; $this->eventApplyMap = $eventApplyMap; + $this->eventClassMap = $eventClassMap; } /** @@ -134,6 +140,19 @@ private function apply(GenericJsonSchemaEvent $event): void { $apply = $this->eventApplyMap[$event->messageName()]; + if (array_key_exists($event->messageName(), $this->eventClassMap)) { + $eventClass = $this->eventClassMap[$event->messageName()]; + + if (! is_callable([$eventClass, 'fromArray'])) { + throw new \RuntimeException(sprintf( + 'Custom event class %s should have a static fromArray method', + $eventClass + )); + } + + $event = ([$eventClass, 'fromArray'])($event->toArray()); + } + if ($this->aggregateState === null) { $newArState = $apply($event); } else { diff --git a/src/Commanding/CommandProcessor.php b/src/Commanding/CommandProcessor.php index 305a68b..10dd082 100644 --- a/src/Commanding/CommandProcessor.php +++ b/src/Commanding/CommandProcessor.php @@ -16,7 +16,7 @@ use Prooph\EventMachine\Aggregate\ContextProvider; use Prooph\EventMachine\Aggregate\Exception\AggregateNotFound; use Prooph\EventMachine\Aggregate\GenericAggregateRoot; -use Prooph\EventMachine\Eventing\GenericJsonSchemaEvent; +use Prooph\EventMachine\Messaging\GenericJsonSchemaEvent; use Prooph\EventSourcing\Aggregate\AggregateRepository; use Prooph\EventSourcing\Aggregate\AggregateType; use Prooph\EventStore\EventStore; @@ -30,6 +30,11 @@ final class CommandProcessor */ private $commandName; + /** + * @var string|null + */ + private $commandClass; + /** * @var string */ @@ -60,6 +65,11 @@ final class CommandProcessor */ private $eventApplyMap; + /** + * @var array + */ + private $eventClassMap; + /** * @var string */ @@ -141,7 +151,9 @@ public static function fromDescriptionArrayAndDependencies( $messageFactory, $eventStore, $snapshotStore, - $contextProvider + $contextProvider, + $description['commandClass'] ?? null, + $description['eventClassMap'] ?? [] ); } @@ -157,7 +169,9 @@ public function __construct( MessageFactory $messageFactory, EventStore $eventStore, SnapshotStore $snapshotStore = null, - ContextProvider $contextProvider = null + ContextProvider $contextProvider = null, + string $commandClass = null, + array $eventClassMap = [] ) { $this->commandName = $commandName; $this->aggregateType = $aggregateType; @@ -171,6 +185,8 @@ public function __construct( $this->eventStore = $eventStore; $this->snapshotStore = $snapshotStore; $this->contextProvider = $contextProvider; + $this->commandClass = $commandClass; + $this->eventClassMap = $eventClassMap; } public function __invoke(GenericJsonSchemaCommand $command) @@ -194,9 +210,18 @@ public function __invoke(GenericJsonSchemaCommand $command) $arId = (string) $payload[$this->aggregateIdentifier]; $arRepository = $this->getAggregateRepository($arId); $arFuncArgs = []; + $commandUuid = $command->uuid()->toString(); + + if ($this->commandClass) { + if (! is_callable([$this->commandClass, 'fromArray'])) { + throw new \RuntimeException(sprintf('Custom command class %s should have a static fromArray method', $this->commandClass)); + } + + $command = ([$this->commandClass, 'fromArray'])($command->toArray()); + } if ($this->createAggregate) { - $aggregate = new GenericAggregateRoot($arId, AggregateType::fromString($this->aggregateType), $this->eventApplyMap); + $aggregate = new GenericAggregateRoot($arId, AggregateType::fromString($this->aggregateType), $this->eventApplyMap, $this->eventClassMap); $arFuncArgs[] = $command; } else { /** @var GenericAggregateRoot $aggregate */ @@ -231,16 +256,38 @@ public function __invoke(GenericJsonSchemaCommand $command) } if (! is_array($event) || ! array_key_exists(0, $event) || ! array_key_exists(1, $event) - || ! is_string($event[0]) || ! is_array($event[1])) { + || ! is_string($event[0]) + || (! is_array($event[1]) && ! is_object($event[1]))) { throw new \RuntimeException(sprintf( - 'Event returned by aggregate of type %s while handling command %s does not has the format [string eventName, array payload]!', + 'Event returned by aggregate of type %s while handling command %s does not have the format [string eventName, array payload | object event]!', $this->aggregateType, $this->commandName )); } + + $customEvent = null; + [$eventName, $payload] = $event; - $metadata = []; + if (is_array($payload)) { + $metadata = []; + } else { + //Custom event class used instead of payload array + if (! method_exists($payload, 'toArray')) { + throw new \RuntimeException(sprintf( + 'Event %s returned by aggregate of type %s while handling command %s should have a toArray method', + get_class($payload), + $this->aggregateType, + $this->commandName + )); + } + + $evtArr = $payload->toArray(); + + $payload = $evtArr['payload'] ?? $evtArr; + + $metadata = $evtArr['metadata'] ?? []; + } if (array_key_exists(2, $event)) { $metadata = $event[2]; @@ -258,7 +305,7 @@ public function __invoke(GenericJsonSchemaCommand $command) $event = $this->messageFactory->createMessageFromArray($eventName, [ 'payload' => $payload, 'metadata' => array_merge([ - '_causation_id' => $command->uuid()->toString(), + '_causation_id' => $commandUuid, '_causation_name' => $this->commandName, ], $metadata), ]); @@ -275,7 +322,7 @@ private function getAggregateRepository(string $aggregateId): AggregateRepositor $this->aggregateRepository = new AggregateRepository( $this->eventStore, AggregateType::fromString($this->aggregateType), - new ClosureAggregateTranslator($aggregateId, $this->eventApplyMap), + new ClosureAggregateTranslator($aggregateId, $this->eventApplyMap, $this->eventClassMap), $this->snapshotStore, new StreamName($this->streamName) ); diff --git a/src/Commanding/CommandProcessorDescription.php b/src/Commanding/CommandProcessorDescription.php index 9a42837..3fcf253 100644 --- a/src/Commanding/CommandProcessorDescription.php +++ b/src/Commanding/CommandProcessorDescription.php @@ -11,8 +11,8 @@ namespace Prooph\EventMachine\Commanding; -use Prooph\EventMachine\Eventing\EventRecorderDescription; use Prooph\EventMachine\EventMachine; +use Prooph\EventMachine\Messaging\EventRecorderDescription; final class CommandProcessorDescription { @@ -26,6 +26,11 @@ final class CommandProcessorDescription */ private $commandName; + /** + * @var string|null + */ + private $commandClass; + /** * @var bool */ @@ -53,10 +58,11 @@ final class CommandProcessorDescription */ private $contextProvider; - public function __construct(string $commandName, EventMachine $eventMachine) + public function __construct(string $commandName, EventMachine $eventMachine, string $commandClass = null) { $this->commandName = $commandName; $this->eventMachine = $eventMachine; + $this->commandClass = $commandClass; } public function withNew(string $aggregateType): self @@ -147,6 +153,7 @@ public function __invoke(): array return [ 'commandName' => $this->commandName, + 'commandClass' => $this->commandClass, 'createAggregate' => $this->createAggregate, 'aggregateType' => $this->aggregateType, 'aggregateIdentifier' => $this->aggregateIdentifier, diff --git a/src/Commanding/CommandToProcessorRouter.php b/src/Commanding/CommandToProcessorRouter.php index b3524f1..1b73ff1 100644 --- a/src/Commanding/CommandToProcessorRouter.php +++ b/src/Commanding/CommandToProcessorRouter.php @@ -33,6 +33,11 @@ final class CommandToProcessorRouter extends AbstractPlugin */ private $aggregateDescriptions; + /** + * @var array + */ + private $eventClassMap; + /** * @var MessageFactory */ @@ -56,6 +61,7 @@ final class CommandToProcessorRouter extends AbstractPlugin public function __construct( array $routingMap, array $aggregateDescriptions, + array $eventClassMap, MessageFactory $messageFactory, EventStore $eventStore, ContextProviderFactory $providerFactory, @@ -63,6 +69,7 @@ public function __construct( ) { $this->routingMap = $routingMap; $this->aggregateDescriptions = $aggregateDescriptions; + $this->eventClassMap = $eventClassMap; $this->messageFactory = $messageFactory; $this->eventStore = $eventStore; $this->contextProviderFactory = $providerFactory; @@ -103,6 +110,7 @@ public function onRouteMessage(ActionEvent $actionEvent): void } $processorDesc['eventApplyMap'] = $aggregateDesc['eventApplyMap']; + $processorDesc['eventClassMap'] = $this->eventClassMap; $contextProvider = $processorDesc['contextProvider'] ? $this->contextProviderFactory->build($processorDesc['contextProvider']) : null; diff --git a/src/EventMachine.php b/src/EventMachine.php index f122994..e60cc6f 100644 --- a/src/EventMachine.php +++ b/src/EventMachine.php @@ -35,6 +35,7 @@ use Prooph\EventMachine\JsonSchema\Type\EnumType; use Prooph\EventMachine\JsonSchema\Type\ObjectType; use Prooph\EventMachine\Messaging\GenericJsonSchemaMessageFactory; +use Prooph\EventMachine\Messaging\MessageTranslatorPlugin; use Prooph\EventMachine\Persistence\Stream; use Prooph\EventMachine\Persistence\TransactionManager as BusTransactionManager; use Prooph\EventMachine\Projecting\ProjectionDescription; @@ -79,12 +80,17 @@ final class EventMachine /** * Map of command names and corresponding json schema of payload * - * Json schema can be passed as array or path to schema file - * * @var array */ private $commandMap = []; + /** + * Map of command names and corresponding command classes (if set during registration) + * + * @var array + */ + private $commandClassMap = []; + /** * Map of command names and corresponding list of preprocessors given as either service id string or callable * @@ -116,6 +122,13 @@ final class EventMachine */ private $eventMap = []; + /** + * Map of event names and corresponding event classes (if set during registration) + * + * @var array + */ + private $eventClassMap = []; + /** * Map of event names and corresponding list of listeners given as either service id string or callable * @@ -145,6 +158,13 @@ final class EventMachine */ private $queryMap = []; + /** + * Map of query names and corresponding query classes (if set during registration) + * + * @var array + */ + private $queryClasaMap = []; + /** * @var array list of type definitions indexed by type name */ @@ -221,13 +241,16 @@ public static function fromCachedConfig(array $config, ContainerInterface $conta } $self->commandMap = $config['commandMap']; + $self->commandClassMap = $config['commandClassMap'] ?? []; $self->eventMap = $config['eventMap']; + $self->eventClassMap = $config['eventClassMap'] ?? []; $self->compiledCommandRouting = $config['compiledCommandRouting']; $self->aggregateDescriptions = $config['aggregateDescriptions']; $self->eventRouting = $config['eventRouting']; $self->compiledProjectionDescriptions = $config['compiledProjectionDescriptions']; $self->compiledQueryDescriptions = $config['compiledQueryDescriptions']; $self->queryMap = $config['queryMap']; + $self->queryClasaMap = $config['queryClassMap'] ?? []; $self->schemaTypes = $config['schemaTypes']; $self->appVersion = $config['appVersion']; $self->writeModelStreamName = $config['writeModelStreamName']; @@ -270,7 +293,7 @@ public function immediateConsistency(): bool return $this->immediateConsistency; } - public function registerCommand(string $commandName, ObjectType $schema): self + public function registerCommand(string $commandName, ObjectType $schema, string $commandClass = null): self { $this->assertNotInitialized(__METHOD__); if (array_key_exists($commandName, $this->commandMap)) { @@ -279,10 +302,14 @@ public function registerCommand(string $commandName, ObjectType $schema): self $this->commandMap[$commandName] = $schema->toArray(); + if ($commandClass) { + $this->commandClassMap[$commandName] = $commandClass; + } + return $this; } - public function registerEvent(string $eventName, ObjectType $schema): self + public function registerEvent(string $eventName, ObjectType $schema, string $eventClass = null): self { $this->assertNotInitialized(__METHOD__); @@ -292,10 +319,14 @@ public function registerEvent(string $eventName, ObjectType $schema): self $this->eventMap[$eventName] = $schema->toArray(); + if ($eventClass) { + $this->eventClassMap[$eventName] = $eventClass; + } + return $this; } - public function registerQuery(string $queryName, ObjectType $payloadSchema = null): QueryDescription + public function registerQuery(string $queryName, ObjectType $payloadSchema = null, string $queryClass = null): QueryDescription { if ($payloadSchema) { $payloadSchema = $payloadSchema->toArray(); @@ -312,6 +343,10 @@ public function registerQuery(string $queryName, ObjectType $payloadSchema = nul $queryDesc = new QueryDescription($queryName, $this); $this->queryDescriptions[$queryName] = $queryDesc; + if ($queryClass) { + $this->queryClasaMap[$queryName] = $queryClass; + } + return $queryDesc; } @@ -397,7 +432,7 @@ public function process(string $commandName): CommandProcessorDescription throw new \BadMethodCallException("Command $commandName is unknown. You should register it first."); } - $this->commandRouting[$commandName] = new CommandProcessorDescription($commandName, $this); + $this->commandRouting[$commandName] = new CommandProcessorDescription($commandName, $this, $this->commandClassMap[$commandName] ?? null); return $this->commandRouting[$commandName]; } @@ -587,7 +622,7 @@ public function loadAggregateState(string $aggregateType, string $aggregateId) $arRepository = new AggregateRepository( $this->container->get(self::SERVICE_ID_EVENT_STORE), AggregateType::fromString($aggregateType), - new ClosureAggregateTranslator($aggregateId, $aggregateDesc['eventApplyMap']), + new ClosureAggregateTranslator($aggregateId, $aggregateDesc['eventApplyMap'], $this->eventClassMap), $snapshotStore, new StreamName($this->writeModelStreamName()) ); @@ -610,6 +645,7 @@ public function runProjections(bool $keepRunning = true, array $projectionOption $this->projectionRunner = new ProjectionRunner( $this->container->get(self::SERVICE_ID_PROJECTION_MANAGER), $this->compiledProjectionDescriptions, + $this->eventClassMap, $this ); } @@ -657,13 +693,16 @@ public function compileCacheableConfig(): array return [ 'commandMap' => $this->commandMap, + 'commandClassMap' => $this->commandClassMap, 'eventMap' => $this->eventMap, + 'eventClassMap' => $this->eventClassMap, 'compiledCommandRouting' => $this->compiledCommandRouting, 'aggregateDescriptions' => $this->aggregateDescriptions, 'eventRouting' => $this->eventRouting, 'compiledProjectionDescriptions' => $this->compiledProjectionDescriptions, 'compiledQueryDescriptions' => $this->compiledQueryDescriptions, 'queryMap' => $this->queryMap, + 'queryClassMap' => $this->queryClasaMap, 'schemaTypes' => $this->schemaTypes, 'appVersion' => $this->appVersion, 'writeModelStreamName' => $this->writeModelStreamName, @@ -878,6 +917,7 @@ private function attachRouterToCommandBus(): void $router = new CommandToProcessorRouter( $this->compiledCommandRouting, $this->aggregateDescriptions, + $this->eventClassMap, $this->container->get(self::SERVICE_ID_MESSAGE_FACTORY), $this->container->get(self::SERVICE_ID_EVENT_STORE), new ContextProviderFactory($this->container), @@ -905,6 +945,11 @@ private function setUpQueryBus(): void $serviceLocatorPlugin = new ServiceLocatorPlugin($this->container); $serviceLocatorPlugin->attachToMessageBus($queryBus); + + if (count($this->queryClasaMap)) { + $queryTranslator = new MessageTranslatorPlugin($this->queryClasaMap); + $queryTranslator->attachToMessageBus($queryBus); + } } private function setUpEventBus(): void @@ -927,6 +972,12 @@ private function setUpEventBus(): void $serviceLocatorPlugin = new ServiceLocatorPlugin($this->container); $serviceLocatorPlugin->attachToMessageBus($eventBus); + + if (count($this->eventClassMap)) { + $eventTranslator = new MessageTranslatorPlugin($this->eventClassMap); + + $eventTranslator->attachToMessageBus($eventBus); + } } private function attachEventPublisherToEventStore(): void diff --git a/src/Eventing/EventRecorderDescription.php b/src/Messaging/EventRecorderDescription.php similarity index 97% rename from src/Eventing/EventRecorderDescription.php rename to src/Messaging/EventRecorderDescription.php index 2773057..b01c11f 100644 --- a/src/Eventing/EventRecorderDescription.php +++ b/src/Messaging/EventRecorderDescription.php @@ -9,7 +9,7 @@ declare(strict_types=1); -namespace Prooph\EventMachine\Eventing; +namespace Prooph\EventMachine\Messaging; use Prooph\EventMachine\Commanding\CommandProcessorDescription; diff --git a/src/Eventing/GenericJsonSchemaEvent.php b/src/Messaging/GenericJsonSchemaEvent.php similarity index 88% rename from src/Eventing/GenericJsonSchemaEvent.php rename to src/Messaging/GenericJsonSchemaEvent.php index a46576f..9ff2f41 100644 --- a/src/Eventing/GenericJsonSchemaEvent.php +++ b/src/Messaging/GenericJsonSchemaEvent.php @@ -9,10 +9,9 @@ declare(strict_types=1); -namespace Prooph\EventMachine\Eventing; +namespace Prooph\EventMachine\Messaging; use Prooph\Common\Messaging\DomainMessage; -use Prooph\EventMachine\Messaging\GenericJsonSchemaMessage; use Prooph\ServiceBus\Async\AsyncMessage; final class GenericJsonSchemaEvent extends GenericJsonSchemaMessage implements AsyncMessage diff --git a/src/Messaging/GenericJsonSchemaMessageFactory.php b/src/Messaging/GenericJsonSchemaMessageFactory.php index 877348c..f777a78 100644 --- a/src/Messaging/GenericJsonSchemaMessageFactory.php +++ b/src/Messaging/GenericJsonSchemaMessageFactory.php @@ -16,7 +16,6 @@ use Prooph\Common\Messaging\Message; use Prooph\Common\Messaging\MessageFactory; use Prooph\EventMachine\Commanding\GenericJsonSchemaCommand; -use Prooph\EventMachine\Eventing\GenericJsonSchemaEvent; use Prooph\EventMachine\JsonSchema\JsonSchemaAssertion; use Prooph\EventMachine\Querying\GenericJsonSchemaQuery; use Ramsey\Uuid\Uuid; diff --git a/src/Messaging/MessageTranslatorPlugin.php b/src/Messaging/MessageTranslatorPlugin.php new file mode 100644 index 0000000..bc0a0d6 --- /dev/null +++ b/src/Messaging/MessageTranslatorPlugin.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Prooph\EventMachine\Messaging; + +use Prooph\Common\Event\ActionEvent; +use Prooph\ServiceBus\MessageBus; +use Prooph\ServiceBus\Plugin\AbstractPlugin; + +final class MessageTranslatorPlugin extends AbstractPlugin +{ + /** + * @var array + */ + private $messageClassMap; + + public function __construct(array $messageClassMap) + { + $this->messageClassMap = $messageClassMap; + } + + public function attachToMessageBus(MessageBus $messageBus): void + { + //Hook in after routing to make sure that router uses GenericJsonSchemaMessage along with message name registered in Event Machine + $this->listenerHandlers = $messageBus->attach( + MessageBus::EVENT_DISPATCH, + [$this, 'onPostRoute'], + MessageBus::PRIORITY_ROUTE + 1 + ); + } + + public function onPostRoute(ActionEvent $e): void + { + $msgName = $e->getParam(MessageBus::EVENT_PARAM_MESSAGE_NAME); + + if (! array_key_exists($msgName, $this->messageClassMap)) { + return; + } + + /** @var GenericJsonSchemaMessage $msg */ + $msg = $e->getParam(MessageBus::EVENT_PARAM_MESSAGE); + + if (! $msg instanceof GenericJsonSchemaMessage) { + return; + } + + $msgClass = $this->messageClassMap[$msgName]; + + if (! is_callable([$msgClass, 'fromArray'])) { + throw new \RuntimeException(sprintf( + 'Custom message class %s should have a static fromArray method', + $msgClass + )); + } + + $msg = ([$msgClass, 'fromArray'])($msg->toArray()); + + $e->setParam(MessageBus::EVENT_PARAM_MESSAGE, $msg); + } +} diff --git a/src/Projecting/AggregateProjector.php b/src/Projecting/AggregateProjector.php index dc83ea8..27d5fec 100644 --- a/src/Projecting/AggregateProjector.php +++ b/src/Projecting/AggregateProjector.php @@ -83,8 +83,12 @@ public function setDataConverter(DataConverter $dataConverter): void $this->dataConverter = $dataConverter; } - public function handle(string $appVersion, string $projectionName, Message $event): void + public function handle(string $appVersion, string $projectionName, $event): void { + if (! $event instanceof Message) { + throw new \RuntimeException(__CLASS__ . ' requires event to be an instance of ' . Message::class . '. Got ' . (is_object($event) ? get_class($event) : gettype($event))); + } + $aggregateId = $event->metadata()['_aggregate_id'] ?? null; if (! $aggregateId) { diff --git a/src/Projecting/ProjectionRunner.php b/src/Projecting/ProjectionRunner.php index bce76e2..228df7d 100644 --- a/src/Projecting/ProjectionRunner.php +++ b/src/Projecting/ProjectionRunner.php @@ -26,6 +26,11 @@ final class ProjectionRunner */ private $projection; + /** + * @var array + */ + private $eventClassMap; + /** * @var bool */ @@ -39,6 +44,7 @@ public static function eventMachineProjectionName(string $appVersion): string public function __construct( ProjectionManager $projectionManager, array $projectionDescriptions, + array $eventClassMap, EventMachine $eventMachine, array $projectionOptions = null) { @@ -72,6 +78,7 @@ public function __construct( self::eventMachineProjectionName($eventMachine->appVersion()), new ReadModelProxy( $projectionDescriptions, + $eventClassMap, $eventMachine ), $projectionOptions diff --git a/src/Projecting/Projector.php b/src/Projecting/Projector.php index c47b1bb..4b3a231 100644 --- a/src/Projecting/Projector.php +++ b/src/Projecting/Projector.php @@ -11,8 +11,6 @@ namespace Prooph\EventMachine\Projecting; -use Prooph\EventMachine\Messaging\Message; - /** * Projections are rebuilt on each deployment * @@ -31,7 +29,7 @@ interface Projector { public function prepareForRun(string $appVersion, string $projectionName): void; - public function handle(string $appVersion, string $projectionName, Message $event): void; + public function handle(string $appVersion, string $projectionName, $event): void; public function deleteReadModel(string $appVersion, string $projectionName): void; } diff --git a/src/Projecting/ReadModel.php b/src/Projecting/ReadModel.php index da7e64f..3174171 100644 --- a/src/Projecting/ReadModel.php +++ b/src/Projecting/ReadModel.php @@ -12,6 +12,7 @@ namespace Prooph\EventMachine\Projecting; use Prooph\EventMachine\EventMachine; +use Prooph\EventMachine\Messaging\GenericJsonSchemaEvent; use Prooph\EventMachine\Messaging\Message; use Prooph\EventMachine\Persistence\Stream; @@ -22,6 +23,11 @@ final class ReadModel */ private $desc; + /** + * @var array + */ + private $eventClassMap; + /** * @var Stream */ @@ -37,16 +43,17 @@ final class ReadModel */ private $appVersion; - public static function fromProjectionDescription(array $desc, EventMachine $eventMachine): ReadModel + public static function fromProjectionDescription(array $desc, array $eventClassMap, EventMachine $eventMachine): ReadModel { $projector = $eventMachine->loadProjector($desc[ProjectionDescription::PROJECTOR_SERVICE_ID]); - return new self($desc, $projector, $eventMachine->appVersion()); + return new self($desc, $eventClassMap, $projector, $eventMachine->appVersion()); } - private function __construct(array $desc, Projector $projector, string $appVersion) + private function __construct(array $desc, array $eventClassMap, Projector $projector, string $appVersion) { $this->desc = $desc; + $this->eventClassMap = $eventClassMap; $this->sourceStream = Stream::fromArray($this->desc[ProjectionDescription::SOURCE_STREAM]); $this->projector = $projector; $this->appVersion = $appVersion; @@ -86,6 +93,14 @@ public function prepareForRun(): void public function handle(Message $event): void { + if (! $this->projector instanceof AggregateProjector + && $event instanceof GenericJsonSchemaEvent + && array_key_exists($event->messageName(), $this->eventClassMap)) { + $evtClass = $this->eventClassMap[$event->messageName()]; + + $event = ([$evtClass, 'fromArray'])($event->toArray()); + } + $this->projector->handle($this->appVersion, $this->desc[ProjectionDescription::PROJECTION_NAME], $event); } diff --git a/src/Projecting/ReadModelProxy.php b/src/Projecting/ReadModelProxy.php index 5f048c3..0e34ca6 100644 --- a/src/Projecting/ReadModelProxy.php +++ b/src/Projecting/ReadModelProxy.php @@ -23,6 +23,11 @@ final class ReadModelProxy extends AbstractReadModel */ private $projectionDescriptions; + /** + * @var array + */ + private $eventClassMap; + /** * @var EventMachine */ @@ -35,9 +40,11 @@ final class ReadModelProxy extends AbstractReadModel public function __construct( array $projectionDescriptions, + array $eventClassMap, EventMachine $eventMachine) { $this->projectionDescriptions = $projectionDescriptions; + $this->eventClassMap = $eventClassMap; $this->eventMachine = $eventMachine; } @@ -58,7 +65,7 @@ public function init(): void $stream = Stream::fromArray($desc[ProjectionDescription::SOURCE_STREAM]); if ($stream->isLocalService()) { - $readModel = ReadModel::fromProjectionDescription($desc, $this->eventMachine); + $readModel = ReadModel::fromProjectionDescription($desc, $this->eventClassMap, $this->eventMachine); $readModel->prepareForRun(); $this->readModels[] = $readModel; } diff --git a/tests/Aggregate/GenericAggregateRootTest.php b/tests/Aggregate/GenericAggregateRootTest.php index 9ba5226..996712b 100644 --- a/tests/Aggregate/GenericAggregateRootTest.php +++ b/tests/Aggregate/GenericAggregateRootTest.php @@ -14,8 +14,8 @@ use Prooph\Common\Messaging\Message; use Prooph\EventMachine\Aggregate\ClosureAggregateTranslator; use Prooph\EventMachine\Aggregate\GenericAggregateRoot; -use Prooph\EventMachine\Eventing\GenericJsonSchemaEvent; use Prooph\EventMachine\JsonSchema\JsonSchema; +use Prooph\EventMachine\Messaging\GenericJsonSchemaEvent; use Prooph\EventMachineTest\BasicTestCase; use Prooph\EventSourcing\Aggregate\AggregateType; use Ramsey\Uuid\Uuid; @@ -42,7 +42,7 @@ public function it_records_events_and_can_be_reconstituted_by_them() $arId = Uuid::uuid4()->toString(); - $user = new GenericAggregateRoot($arId, AggregateType::fromString('User'), $eventApplyMap); + $user = new GenericAggregateRoot($arId, AggregateType::fromString('User'), $eventApplyMap, []); $userWasRegistered = new GenericJsonSchemaEvent( 'UserWasRegistered', @@ -68,7 +68,7 @@ public function it_records_events_and_can_be_reconstituted_by_them() self::assertCount(2, $recordedEvents); - $translator = new ClosureAggregateTranslator($arId, $eventApplyMap); + $translator = new ClosureAggregateTranslator($arId, $eventApplyMap, []); $sameUser = $translator->reconstituteAggregateFromHistory(AggregateType::fromString('User'), new \ArrayIterator([$recordedEvents[0]])); diff --git a/tests/BasicTestCase.php b/tests/BasicTestCase.php index 66d6fa4..ddce2f6 100644 --- a/tests/BasicTestCase.php +++ b/tests/BasicTestCase.php @@ -16,9 +16,9 @@ use Prooph\EventMachine\Aggregate\ClosureAggregateTranslator; use Prooph\EventMachine\Aggregate\GenericAggregateRoot; use Prooph\EventMachine\Commanding\GenericJsonSchemaCommand; -use Prooph\EventMachine\Eventing\GenericJsonSchemaEvent; use Prooph\EventMachine\JsonSchema\JsonSchemaAssertion; use Prooph\EventMachine\JsonSchema\JustinRainbowJsonSchemaAssertion; +use Prooph\EventMachine\Messaging\GenericJsonSchemaEvent; use Prophecy\Argument; class BasicTestCase extends TestCase @@ -44,7 +44,7 @@ class BasicTestCase extends TestCase */ protected function extractRecordedEvents(GenericAggregateRoot $aggregateRoot): array { - $aggregateRootTranslator = new ClosureAggregateTranslator('unknown', []); + $aggregateRootTranslator = new ClosureAggregateTranslator('unknown', [], []); return $aggregateRootTranslator->extractPendingStreamEvents($aggregateRoot); } diff --git a/tests/Commanding/CommandProcessorTest.php b/tests/Commanding/CommandProcessorTest.php index 4c9bac6..189c653 100644 --- a/tests/Commanding/CommandProcessorTest.php +++ b/tests/Commanding/CommandProcessorTest.php @@ -13,8 +13,8 @@ use Prooph\EventMachine\Aggregate\ContextProvider; use Prooph\EventMachine\Commanding\CommandProcessor; -use Prooph\EventMachine\Eventing\GenericJsonSchemaEvent; use Prooph\EventMachine\EventMachine; +use Prooph\EventMachine\Messaging\GenericJsonSchemaEvent; use Prooph\EventMachine\Messaging\Message; use Prooph\EventMachineTest\Aggregate\Stub\ContextAwareAggregateDescription; use Prooph\EventMachineTest\BasicTestCase; diff --git a/tests/Commanding/CommandToProcessorRouterTest.php b/tests/Commanding/CommandToProcessorRouterTest.php index ca1b657..a988259 100644 --- a/tests/Commanding/CommandToProcessorRouterTest.php +++ b/tests/Commanding/CommandToProcessorRouterTest.php @@ -53,6 +53,8 @@ public function it_sets_command_processor_as_command_handler() ], ]; + $eventClassMap = []; + $messageFactory = $this->prophesize(MessageFactory::class); $eventStore = $this->prophesize(EventStore::class); $snapshotStore = $this->prophesize(SnapshotStore::class); @@ -63,6 +65,7 @@ public function it_sets_command_processor_as_command_handler() $router = new CommandToProcessorRouter( $commandMap, $aggregateDescriptions, + $eventClassMap, $messageFactory->reveal(), $eventStore->reveal(), $contextProviderFactory->reveal(), diff --git a/tests/CustomMessages/CustomMessagesTest.php b/tests/CustomMessages/CustomMessagesTest.php new file mode 100644 index 0000000..9f96c39 --- /dev/null +++ b/tests/CustomMessages/CustomMessagesTest.php @@ -0,0 +1,204 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Prooph\EventMachineTest\CustomMessages; + +use Prooph\EventMachine\Container\EventMachineContainer; +use Prooph\EventMachine\EventMachine; +use Prooph\EventMachine\Persistence\Stream; +use Prooph\EventMachineTest\BasicTestCase; +use Prooph\EventMachineTest\CustomMessages\Stub\Aggregate\Todo; +use Prooph\EventMachineTest\CustomMessages\Stub\Descrption\TodoDescription; +use Prooph\EventMachineTest\CustomMessages\Stub\Event\TodoMarkedAsDone; +use Prooph\EventMachineTest\CustomMessages\Stub\Event\TodoPosted; +use Prooph\EventMachineTest\CustomMessages\Stub\Projection\TodoProjector; +use Prooph\EventMachineTest\CustomMessages\Stub\Query\GetDoneTodos; +use Prooph\EventMachineTest\CustomMessages\Stub\Query\GetTodo; +use Prooph\EventMachineTest\CustomMessages\Stub\Query\TodoFinder; +use Ramsey\Uuid\Uuid; + +class CustomMessagesTest extends BasicTestCase +{ + /** + * @test + */ + public function it_passes_custom_messages_to_userland_code_if_registered() + { + $eventMachine = new EventMachine(); + + $eventMachine->load(TodoDescription::class); + + $pmEvt = null; + + $eventMachine->on(TodoDescription::EVT_TODO_POSTED, function (TodoPosted $evt) use (&$pmEvt) { + $pmEvt = $evt; + }); + + $eventMachine->watch(Stream::ofWriteModel()) + ->with('TodoProjection', TodoProjector::class); + + $todoFinder = new TodoFinder(); + $todoProjector = new TodoProjector(); + + $eventMachine->initialize(new EventMachineContainer($eventMachine)); + + $eventMachine->bootstrapInTestMode([], [ + TodoFinder::class => $todoFinder, + TodoProjector::class => $todoProjector, + ]); + + $todoId = Uuid::uuid4()->toString(); + + $postTodo = $eventMachine->messageFactory()->createMessageFromArray( + TodoDescription::CMD_POST_TODO, + [ + 'payload' => [ + 'todoId' => $todoId, + 'text' => 'Test todo', + ], + ] + ); + + $eventMachine->dispatch($postTodo); + + $expectedTodo = [ + 'todoId' => $todoId, + 'text' => 'Test todo', + ]; + + $recordedEvents = $eventMachine->popRecordedEventsOfTestSession(); + + $this->assertCount(1, $recordedEvents); + + $this->assertEquals($expectedTodo, $recordedEvents[0]->payload()); + //Test that custom event metadata is passed along + $this->assertEquals('test', $recordedEvents[0]->metadata()['meta']); + + $todo = $eventMachine->loadAggregateState(Todo::class, $todoId); + + $this->assertEquals([ + 'todoId' => $todoId, + 'text' => 'Test todo', + ], $todo); + + $this->assertInstanceOf(TodoPosted::class, $pmEvt); + + //Verify that projections receive custom events + $eventMachine->runProjections(false); + + $this->assertInstanceOf(TodoPosted::class, $todoProjector->getLastHandledEvent()); + + //Verify that finders receive custom queries + $getTodo = $eventMachine->messageFactory()->createMessageFromArray( + TodoDescription::QRY_GET_TODO, + [ + 'payload' => [ + 'todoId' => $todoId, + ], + ] + ); + + $eventMachine->dispatch($getTodo); + + $this->assertInstanceOf(GetTodo::class, $todoFinder->getLastReceivedQuery()); + $this->assertEquals($todoId, $todoFinder->getLastReceivedQuery()->todoId()); + } + + /** + * @test + */ + public function it_passes_prooph_messages_to_userland_code_if_registered() + { + $eventMachine = new EventMachine(); + + $eventMachine->load(TodoDescription::class); + + $pmEvt = null; + + $eventMachine->on(TodoDescription::EVT_TODO_MAKRED_AS_DONE, function (TodoMarkedAsDone $evt) use (&$pmEvt) { + $pmEvt = $evt; + }); + + $eventMachine->watch(Stream::ofWriteModel()) + ->with('TodoProjection', TodoProjector::class); + + $todoFinder = new TodoFinder(); + $todoProjector = new TodoProjector(); + + $eventMachine->initialize(new EventMachineContainer($eventMachine)); + + $todoId = Uuid::uuid4()->toString(); + + $eventMachine->bootstrapInTestMode([ + $eventMachine->messageFactory()->createMessageFromArray( + TodoDescription::EVT_TODO_POSTED, + [ + 'payload' => [ + 'todoId' => $todoId, + 'text' => 'Test todo', + ], + ] + ), + ], [ + TodoFinder::class => $todoFinder, + TodoProjector::class => $todoProjector, + ]); + + $markAsDone = $eventMachine->messageFactory()->createMessageFromArray( + TodoDescription::CMD_MARK_AS_DONE, + [ + 'payload' => [ + 'todoId' => $todoId, + ], + ] + ); + + $eventMachine->dispatch($markAsDone); + + $recordedEvents = $eventMachine->popRecordedEventsOfTestSession(); + + $this->assertCount(1, $recordedEvents); + + $this->assertEquals(['todoId' => $todoId], $recordedEvents[0]->payload()); + //Test that custom event metadata is passed along + $this->assertEquals('test', $recordedEvents[0]->metadata()['meta']); + + $todo = $eventMachine->loadAggregateState(Todo::class, $todoId); + + $this->assertEquals([ + 'todoId' => $todoId, + 'text' => 'Test todo', + 'done' => true, + ], $todo); + + $this->assertInstanceOf(TodoMarkedAsDone::class, $pmEvt); + + //Verify that projections receive custom events + $eventMachine->runProjections(false); + + $this->assertInstanceOf(TodoMarkedAsDone::class, $todoProjector->getLastHandledEvent()); + + //Verify that finders receive custom queries + $getDoneTodos = $eventMachine->messageFactory()->createMessageFromArray( + TodoDescription::QRY_GET_DONE_TODOS, + [ + 'payload' => [ + 'todoId' => $todoId, + ], + ] + ); + + $eventMachine->dispatch($getDoneTodos); + + $this->assertInstanceOf(GetDoneTodos::class, $todoFinder->getLastReceivedQuery()); + $this->assertEquals($todoId, $todoFinder->getLastReceivedQuery()->todoId()); + } +} diff --git a/tests/CustomMessages/Stub/Aggregate/Todo.php b/tests/CustomMessages/Stub/Aggregate/Todo.php new file mode 100644 index 0000000..3cb7565 --- /dev/null +++ b/tests/CustomMessages/Stub/Aggregate/Todo.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Prooph\EventMachineTest\CustomMessages\Stub\Aggregate; + +use Prooph\EventMachineTest\CustomMessages\Stub\Command\MarkAsDone; +use Prooph\EventMachineTest\CustomMessages\Stub\Command\PostTodo; +use Prooph\EventMachineTest\CustomMessages\Stub\Descrption\TodoDescription; +use Prooph\EventMachineTest\CustomMessages\Stub\Event\TodoMarkedAsDone; +use Prooph\EventMachineTest\CustomMessages\Stub\Event\TodoPosted; + +final class Todo +{ + public static function post(PostTodo $postTodo): \Generator + { + yield [TodoDescription::EVT_TODO_POSTED, TodoPosted::with( + $postTodo->todoId(), + $postTodo->text() + ), ['meta' => 'test']]; + } + + public static function whenTodoPosted(TodoPosted $todoPosted): array + { + return [ + 'todoId' => $todoPosted->todoId(), + 'text' => $todoPosted->text(), + ]; + } + + public static function markAsDone(array $state, MarkAsDone $cmd): \Generator + { + yield [TodoDescription::EVT_TODO_MAKRED_AS_DONE, TodoMarkedAsDone::with($cmd->todoId())->withAddedMetadata('meta', 'test')]; + } + + public static function whenTodoMarkedAsDone(array $state, TodoMarkedAsDone $markedAsDone): array + { + $state['done'] = true; + + return $state; + } +} diff --git a/tests/CustomMessages/Stub/Command/MarkAsDone.php b/tests/CustomMessages/Stub/Command/MarkAsDone.php new file mode 100644 index 0000000..4daf450 --- /dev/null +++ b/tests/CustomMessages/Stub/Command/MarkAsDone.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Prooph\EventMachineTest\CustomMessages\Stub\Command; + +use Prooph\Common\Messaging\Command; +use Prooph\Common\Messaging\PayloadTrait; + +final class MarkAsDone extends Command +{ + use PayloadTrait; + + public function todoId(): string + { + return $this->payload['todoId']; + } +} diff --git a/tests/CustomMessages/Stub/Command/PostTodo.php b/tests/CustomMessages/Stub/Command/PostTodo.php new file mode 100644 index 0000000..cf49e9d --- /dev/null +++ b/tests/CustomMessages/Stub/Command/PostTodo.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Prooph\EventMachineTest\CustomMessages\Stub\Command; + +final class PostTodo +{ + private $todoId; + + private $text; + + public static function fromArray(array $genericMsgData): PostTodo + { + $self = new self(); + + $self->todoId = (string) $genericMsgData['payload']['todoId'] ?? ''; + $self->text = (string) $genericMsgData['payload']['text'] ?? ''; + + return $self; + } + + public function todoId(): string + { + return $this->todoId; + } + + public function text(): string + { + return $this->text; + } +} diff --git a/tests/CustomMessages/Stub/Descrption/TodoDescription.php b/tests/CustomMessages/Stub/Descrption/TodoDescription.php new file mode 100644 index 0000000..3e4c8b5 --- /dev/null +++ b/tests/CustomMessages/Stub/Descrption/TodoDescription.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Prooph\EventMachineTest\CustomMessages\Stub\Descrption; + +use Prooph\EventMachine\EventMachine; +use Prooph\EventMachine\EventMachineDescription; +use Prooph\EventMachine\JsonSchema\JsonSchema; +use Prooph\EventMachineTest\CustomMessages\Stub\Aggregate\Todo; +use Prooph\EventMachineTest\CustomMessages\Stub\Command\MarkAsDone; +use Prooph\EventMachineTest\CustomMessages\Stub\Command\PostTodo; +use Prooph\EventMachineTest\CustomMessages\Stub\Event\TodoMarkedAsDone; +use Prooph\EventMachineTest\CustomMessages\Stub\Event\TodoPosted; +use Prooph\EventMachineTest\CustomMessages\Stub\Query\GetDoneTodos; +use Prooph\EventMachineTest\CustomMessages\Stub\Query\GetTodo; +use Prooph\EventMachineTest\CustomMessages\Stub\Query\TodoFinder; + +final class TodoDescription implements EventMachineDescription +{ + const CMD_POST_TODO = 'Test.PostTodo'; + const CMD_MARK_AS_DONE = 'Test.MarkAsDone'; + + const EVT_TODO_POSTED = 'Test.TodoPosted'; + const EVT_TODO_MAKRED_AS_DONE = 'Test.TodoMarkedAsDone'; + + const QRY_GET_TODO = 'Test.GetTodo'; + const QRY_GET_DONE_TODOS = 'Test.GetDoneTodos'; + + public static function describe(EventMachine $eventMachine): void + { + //Custom DTOs used as messages + $eventMachine->registerCommand(self::CMD_POST_TODO, JsonSchema::object([ + 'todoId' => JsonSchema::uuid(), + 'text' => JsonSchema::string(), + ]), PostTodo::class); + + $eventMachine->registerEvent(self::EVT_TODO_POSTED, JsonSchema::object([ + 'todoId' => JsonSchema::uuid(), + 'text' => JsonSchema::string(), + ]), TodoPosted::class); + + $eventMachine->registerQuery(self::QRY_GET_TODO, JsonSchema::object([ + 'todoId' => JsonSchema::uuid(), + ]), GetTodo::class) + ->resolveWith(TodoFinder::class) + ->setReturnType(JsonSchema::object([ + 'todoId' => JsonSchema::uuid(), + 'text' => JsonSchema::string(), + ])); + + $eventMachine->process(self::CMD_POST_TODO) + ->withNew(Todo::class) + ->identifiedBy('todoId') + ->handle([Todo::class, 'post']) + ->recordThat(self::EVT_TODO_POSTED) + ->apply([Todo::class, 'whenTodoPosted']); + + //prooph messages + $eventMachine->registerCommand(self::CMD_MARK_AS_DONE, JsonSchema::object([ + 'todoId' => JsonSchema::uuid(), + ]), MarkAsDone::class); + + $eventMachine->registerEvent(self::EVT_TODO_MAKRED_AS_DONE, JsonSchema::object([ + 'todoId' => JsonSchema::uuid(), + ]), TodoMarkedAsDone::class); + + $eventMachine->registerQuery(self::QRY_GET_DONE_TODOS, JsonSchema::object([ + 'todoId' => JsonSchema::uuid(), + ]), GetDoneTodos::class) + ->resolveWith(TodoFinder::class) + ->setReturnType(JsonSchema::array(JsonSchema::uuid())); + + $eventMachine->process(self::CMD_MARK_AS_DONE) + ->withExisting(Todo::class) + ->handle([Todo::class, 'markAsDone']) + ->recordThat(self::EVT_TODO_MAKRED_AS_DONE) + ->apply([Todo::class, 'whenTodoMarkedAsDone']); + } +} diff --git a/tests/CustomMessages/Stub/Event/TodoMarkedAsDone.php b/tests/CustomMessages/Stub/Event/TodoMarkedAsDone.php new file mode 100644 index 0000000..c7d65cf --- /dev/null +++ b/tests/CustomMessages/Stub/Event/TodoMarkedAsDone.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Prooph\EventMachineTest\CustomMessages\Stub\Event; + +use Prooph\Common\Messaging\DomainEvent; +use Prooph\Common\Messaging\PayloadTrait; + +final class TodoMarkedAsDone extends DomainEvent +{ + use PayloadTrait; + + public static function with(string $todoId): TodoMarkedAsDone + { + return new self([ + 'todoId' => $todoId, + ]); + } + + public function todoId(): string + { + return $this->payload['todoId']; + } +} diff --git a/tests/CustomMessages/Stub/Event/TodoPosted.php b/tests/CustomMessages/Stub/Event/TodoPosted.php new file mode 100644 index 0000000..65fe176 --- /dev/null +++ b/tests/CustomMessages/Stub/Event/TodoPosted.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Prooph\EventMachineTest\CustomMessages\Stub\Event; + +final class TodoPosted +{ + private $todoId; + + private $text; + + public static function fromArray(array $genericMsgData): TodoPosted + { + return new self( + (string) $genericMsgData['payload']['todoId'] ?? '', + (string) $genericMsgData['payload']['text'] ?? '' + ); + } + + public static function with(string $todoId, string $text): TodoPosted + { + return new self($todoId, $text); + } + + private function __construct(string $todoId, string $text) + { + $this->todoId = $todoId; + $this->text = $text; + } + + public function todoId(): string + { + return $this->todoId; + } + + public function text(): string + { + return $this->text; + } + + public function toArray(): array + { + return [ + 'todoId' => $this->todoId, + 'text' => $this->text, + ]; + } +} diff --git a/tests/CustomMessages/Stub/Projection/TodoProjector.php b/tests/CustomMessages/Stub/Projection/TodoProjector.php new file mode 100644 index 0000000..17179c7 --- /dev/null +++ b/tests/CustomMessages/Stub/Projection/TodoProjector.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Prooph\EventMachineTest\CustomMessages\Stub\Projection; + +use Prooph\EventMachine\Projecting\Projector; + +final class TodoProjector implements Projector +{ + private $lastHandledEvent; + + public function prepareForRun(string $appVersion, string $projectionName): void + { + //nothing to do + } + + public function handle(string $appVersion, string $projectionName, $event): void + { + $this->lastHandledEvent = $event; + } + + public function deleteReadModel(string $appVersion, string $projectionName): void + { + //nothing to do + } + + public function getLastHandledEvent() + { + return $this->lastHandledEvent; + } +} diff --git a/tests/CustomMessages/Stub/Query/GetDoneTodos.php b/tests/CustomMessages/Stub/Query/GetDoneTodos.php new file mode 100644 index 0000000..49c1c4c --- /dev/null +++ b/tests/CustomMessages/Stub/Query/GetDoneTodos.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Prooph\EventMachineTest\CustomMessages\Stub\Query; + +use Prooph\Common\Messaging\PayloadTrait; +use Prooph\Common\Messaging\Query; + +final class GetDoneTodos extends Query +{ + use PayloadTrait; + + public function todoId(): string + { + return $this->payload['todoId']; + } +} diff --git a/tests/CustomMessages/Stub/Query/GetTodo.php b/tests/CustomMessages/Stub/Query/GetTodo.php new file mode 100644 index 0000000..edc4e6e --- /dev/null +++ b/tests/CustomMessages/Stub/Query/GetTodo.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Prooph\EventMachineTest\CustomMessages\Stub\Query; + +final class GetTodo +{ + private $todoId; + + public static function fromArray(array $genericMsgData): GetTodo + { + $self = new self(); + $self->todoId = (string) $genericMsgData['payload']['todoId'] ?? ''; + + return $self; + } + + public function todoId(): string + { + return $this->todoId; + } +} diff --git a/tests/CustomMessages/Stub/Query/TodoFinder.php b/tests/CustomMessages/Stub/Query/TodoFinder.php new file mode 100644 index 0000000..d3dac6f --- /dev/null +++ b/tests/CustomMessages/Stub/Query/TodoFinder.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Prooph\EventMachineTest\CustomMessages\Stub\Query; + +use React\Promise\Deferred; + +final class TodoFinder +{ + private $lastQuery; + + public function __invoke($query, Deferred $deferred) + { + $this->lastQuery = $query; + } + + public function getLastReceivedQuery() + { + return $this->lastQuery; + } +} diff --git a/tests/EventMachineTest.php b/tests/EventMachineTest.php index f823ae2..e27e985 100644 --- a/tests/EventMachineTest.php +++ b/tests/EventMachineTest.php @@ -16,7 +16,6 @@ use Prooph\EventMachine\Commanding\GenericJsonSchemaCommand; use Prooph\EventMachine\Container\ContainerChain; use Prooph\EventMachine\Container\EventMachineContainer; -use Prooph\EventMachine\Eventing\GenericJsonSchemaEvent; use Prooph\EventMachine\EventMachine; use Prooph\EventMachine\Exception\TransactionCommitFailed; use Prooph\EventMachine\JsonSchema\JsonSchema; @@ -24,6 +23,7 @@ use Prooph\EventMachine\JsonSchema\Type\EnumType; use Prooph\EventMachine\JsonSchema\Type\StringType; use Prooph\EventMachine\JsonSchema\Type\UuidType; +use Prooph\EventMachine\Messaging\GenericJsonSchemaEvent; use Prooph\EventMachine\Persistence\DocumentStore; use Prooph\EventMachine\Persistence\DocumentStore\InMemoryDocumentStore; use Prooph\EventMachine\Persistence\InMemoryConnection; diff --git a/tests/Messaging/GenericJsonSchemaMessageFactoryTest.php b/tests/Messaging/GenericJsonSchemaMessageFactoryTest.php index 5e11127..d5ae7df 100644 --- a/tests/Messaging/GenericJsonSchemaMessageFactoryTest.php +++ b/tests/Messaging/GenericJsonSchemaMessageFactoryTest.php @@ -12,10 +12,10 @@ namespace Prooph\EventMachineTest\Messaging; use Prooph\EventMachine\Commanding\GenericJsonSchemaCommand; -use Prooph\EventMachine\Eventing\GenericJsonSchemaEvent; use Prooph\EventMachine\JsonSchema\JsonSchema; use Prooph\EventMachine\JsonSchema\JsonSchemaAssertion; use Prooph\EventMachine\JsonSchema\JustinRainbowJsonSchemaAssertion; +use Prooph\EventMachine\Messaging\GenericJsonSchemaEvent; use Prooph\EventMachine\Messaging\GenericJsonSchemaMessageFactory; use Prooph\EventMachine\Querying\GenericJsonSchemaQuery; use Prooph\EventMachineTest\BasicTestCase; diff --git a/tests/Messaging/GenericJsonSchemaMessageTest.php b/tests/Messaging/GenericJsonSchemaMessageTest.php index 0114ffb..f05e60f 100644 --- a/tests/Messaging/GenericJsonSchemaMessageTest.php +++ b/tests/Messaging/GenericJsonSchemaMessageTest.php @@ -11,8 +11,8 @@ namespace Prooph\EventMachineTest\Messaging; -use Prooph\EventMachine\Eventing\GenericJsonSchemaEvent; use Prooph\EventMachine\JsonSchema\JsonSchema; +use Prooph\EventMachine\Messaging\GenericJsonSchemaEvent; use Prooph\EventMachineTest\BasicTestCase; final class GenericJsonSchemaMessageTest extends BasicTestCase