├── .gitignore ├── phpstan.neon ├── .scrutinizer.yml ├── tests ├── bootstrap.php ├── Prooph │ ├── configs │ │ ├── ServiceBusConfiguratorTests │ │ │ ├── QueryBusConfiguratorTest.neon │ │ │ ├── EventBusConfiguratorTest.neon │ │ │ └── CommandBusConfiguratorTest.neon │ │ ├── EventStoreConfiguratorTest.neon │ │ ├── AsynchronousMessages │ │ │ ├── AsynchronousEventProducerTest.neon │ │ │ ├── AsynchronousQueryProducerTest.neon │ │ │ └── AsynchronousCommandProducerTest.neon │ │ ├── EventSourcingConfiguratorTest.neon │ │ ├── ProjectionManagerTests │ │ │ ├── MysqlProjectionManagerTest.neon │ │ │ └── MariaDbProjectionManagerTest.neon │ │ └── FullTestConfig.neon │ ├── ProjectionManager │ │ ├── FakePDOFactory.php │ │ ├── MysqlProjectionManagerConfiguratorTest.php │ │ └── MariaDbProjectionManagerConfiguratorTest.php │ ├── ProophExtensionTest.php │ ├── EventStore │ │ └── EventStoreConfiguratorTest.php │ ├── EventSourcing │ │ ├── FakeImplementations │ │ │ ├── TestAggregateCreated.php │ │ │ ├── TestAggregateRoot.php │ │ │ └── MemoryTestRepository.php │ │ └── EventSourcingConfiguratorTest.php │ ├── ServiceBus │ │ └── ServiceBusConfigurators │ │ │ ├── CommandBusConfiguratorTest.php │ │ │ ├── EventBusConfiguratorTest.php │ │ │ └── QueryBusConfiguratorTest.php │ ├── AsynchronousMessages │ │ ├── FakeImplementations │ │ │ └── TestAsynchronousMessageProducerBridge.php │ │ ├── Configurators │ │ │ ├── AsynchronousEventProducerConfiguratorTest.php │ │ │ ├── AsynchronousQueryProducerConfiguratorTest.php │ │ │ └── AsynchronousCommandProducerConfiguratorTest.php │ │ ├── Factories │ │ │ └── AbstractAsynchronousMessageProducerFactoryTest.php │ │ └── AsynchronousMessageProducerTest.php │ ├── ProophExtensionTestCase.php │ └── Common │ │ └── NetteContainerWrapperTest.php └── phpunit.xml ├── src ├── AsynchronousMessages │ ├── AsynchronousMessageProducerBridge.php │ ├── Factories │ │ ├── AsynchronousEventProducerFactory.php │ │ ├── AsynchronousQueryProducerFactory.php │ │ ├── AsynchronousCommandProducerFactory.php │ │ └── AbstractAsynchronousMessageProducerFactory.php │ ├── Configurators │ │ ├── AsynchronousEventProducerConfigurator.php │ │ ├── AsynchronousQueryProducerConfigurator.php │ │ ├── AsynchronousCommandProducerConfigurator.php │ │ └── AbstractAsynchronousProducerConfigurator.php │ ├── AsynchronousMessagesConfigurator.php │ └── AsynchronousMessageProducer.php ├── Common │ ├── Configurator.php │ ├── NetteContainerWrapper │ │ ├── ContainerException.php │ │ └── NotFoundException.php │ ├── DefaultConfigurator.php │ ├── CompositeConfigurator.php │ └── NetteContainerWrapper.php ├── ServiceBus │ ├── ServiceBusConfigurators │ │ ├── EventBusConfigurator.php │ │ ├── QueryBusConfigurator.php │ │ ├── CommandBusConfigurator.php │ │ └── AbstractBusConfigurator.php │ └── ServiceBusesConfigurator.php ├── ProophExtensionConfigurator.php ├── EventStore │ └── EventStoreConfigurator.php ├── EventSourcing │ └── EventSourcingConfigurator.php ├── ProjectionManager │ └── ProjectionManagerConfigurator.php └── ProophExtension.php ├── .travis.yml ├── composer.json ├── LICENSE ├── docs ├── KeepLearning.md ├── Configuration.md └── AsynchronousMessaging.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | .idea 3 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | bootstrap: %rootDir%/../../../tests/bootstrap.php 3 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | tools: 2 | external_code_coverage: true 3 | 4 | checks: 5 | php: 6 | code_rating: true 7 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | givenTestConfig('FullTestConfig.neon'); 15 | 16 | $this->whenGenerateContainer($config); 17 | 18 | $this->thenPass(); 19 | } 20 | 21 | protected function whenGenerateContainer(Configurator $config) 22 | { 23 | $config->generateContainer(new Compiler()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/ServiceBus/ServiceBusConfigurators/CommandBusConfigurator.php: -------------------------------------------------------------------------------- 1 | givenTestContainer(self::TEST_CONFIG_PATH); 15 | 16 | $repository = $this->whenGetServiceByTypeFromContainer(EventStore::class); 17 | 18 | $this->thenIsInstanceOfExpectedClass(EventStore::class, $repository); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/phpunit.xml: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | ../src 17 | 18 | 19 | 20 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /tests/Prooph/EventSourcing/FakeImplementations/TestAggregateCreated.php: -------------------------------------------------------------------------------- 1 | self::TEST_PAYLOAD_VALUE, 16 | ]; 17 | 18 | public static function create(UuidInterface $uuid): AggregateChanged 19 | { 20 | return self::occur( 21 | $uuid->toString(), 22 | self::TEST_PAYLOAD 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Common/DefaultConfigurator.php: -------------------------------------------------------------------------------- 1 | extension = $extension; 17 | } 18 | 19 | protected function getContainerBuilder(): ContainerBuilder 20 | { 21 | return $this->extension->getContainerBuilder(); 22 | } 23 | 24 | public function getContainerWrapperServiceId() 25 | { 26 | $containerBuilder = $this->getContainerBuilder(); 27 | 28 | return $containerBuilder->getByType(NetteContainerWrapper::class); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Prooph/EventSourcing/FakeImplementations/TestAggregateRoot.php: -------------------------------------------------------------------------------- 1 | recordThat(TestAggregateCreated::create($uuid)); 18 | 19 | return $instance; 20 | } 21 | 22 | protected function aggregateId(): string 23 | { 24 | return 'fake'; 25 | } 26 | 27 | protected function apply(AggregateChanged $event): void 28 | { 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Prooph/ServiceBus/ServiceBusConfigurators/CommandBusConfiguratorTest.php: -------------------------------------------------------------------------------- 1 | givenTestContainer(self::TEST_CONFIG_PATH); 15 | 16 | $repository = $this->whenGetServiceByTypeFromContainer(CommandBus::class); 17 | 18 | $this->thenIsInstanceOfExpectedClass(CommandBus::class, $repository); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/Prooph/ServiceBus/ServiceBusConfigurators/EventBusConfiguratorTest.php: -------------------------------------------------------------------------------- 1 | givenTestContainer(self::TEST_CONFIG_PATH); 15 | 16 | $repository = $this->whenGetServiceByTypeFromContainer(EventBus::class); 17 | 18 | $this->thenIsInstanceOfExpectedClass(EventBus::class, $repository); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/Prooph/ServiceBus/ServiceBusConfigurators/QueryBusConfiguratorTest.php: -------------------------------------------------------------------------------- 1 | givenTestContainer(self::TEST_CONFIG_PATH); 16 | 17 | $repository = $this->whenGetServiceByTypeFromContainer(QueryBus::class); 18 | 19 | $this->thenIsInstanceOfExpectedClass(QueryBus::class, $repository); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/Prooph/EventSourcing/EventSourcingConfiguratorTest.php: -------------------------------------------------------------------------------- 1 | givenTestContainer(self::TEST_CONFIG_PATH); 16 | 17 | $repository = $this->whenGetServiceByTypeFromContainer(MemoryTestRepository::class); 18 | 19 | $this->thenIsInstanceOfExpectedClass(MemoryTestRepository::class, $repository); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/Prooph/AsynchronousMessages/FakeImplementations/TestAsynchronousMessageProducerBridge.php: -------------------------------------------------------------------------------- 1 | published[] = [ 18 | self::KEY_ROUTING_KEY => $routingKey, 19 | self::KEY_DATA => $data, 20 | ]; 21 | } 22 | 23 | public function getPublished() 24 | { 25 | return $this->published; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "lidskasila/prooph", 3 | "description" : "Nette extension for original Prooph toolbox of CQRS and EventSourcing Infrastructure for PHP.", 4 | "license" : "MIT", 5 | "autoload" : { 6 | "psr-4" : { 7 | "LidskaSila\\Prooph\\" : "src/" 8 | } 9 | }, 10 | "require" : { 11 | "php" : ">=7.0", 12 | "prooph/event-sourcing" : "^5.2", 13 | "prooph/service-bus": "^6.0", 14 | "psr/container": "^1.0", 15 | "sandrokeil/interop-config": "^2.1", 16 | "prooph/event-store-bus-bridge": "^3.0", 17 | "nette/di" : "^2.4", 18 | "prooph/pdo-event-store" : "^1.5" 19 | }, 20 | "autoload-dev" : { 21 | "classmap" : [ 22 | "tests/" 23 | ] 24 | }, 25 | "require-dev": { 26 | "mockery/mockery": "^0.9", 27 | "phpunit/phpunit": "^6.2", 28 | "nette/bootstrap": "^2.4", 29 | "phpstan/phpstan": "^0.7", 30 | "react/promise": "^2.5" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/Prooph/ProjectionManager/MysqlProjectionManagerConfiguratorTest.php: -------------------------------------------------------------------------------- 1 | givenTestContainer(self::TEST_CONFIG_PATH); 17 | 18 | $repository = $this->whenGetServiceByTypeFromContainer(ProjectionManager::class); 19 | 20 | $this->thenIsInstanceOfExpectedClass(MySqlProjectionManager::class, $repository); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/Prooph/ProjectionManager/MariaDbProjectionManagerConfiguratorTest.php: -------------------------------------------------------------------------------- 1 | givenTestContainer(self::TEST_CONFIG_PATH); 17 | 18 | $repository = $this->whenGetServiceByTypeFromContainer(ProjectionManager::class); 19 | 20 | $this->thenIsInstanceOfExpectedClass(MariaDbProjectionManager::class, $repository); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/Prooph/configs/EventSourcingConfiguratorTest.neon: -------------------------------------------------------------------------------- 1 | extensions: 2 | prooph: LidskaSila\Prooph\ProophExtension 3 | 4 | prooph: 5 | event_sourcing: 6 | aggregate_repository: 7 | test_repository: 8 | repository_class: LidskaSila\Prooph\Tests\EventSourcing\FakeImplementations\MemoryTestRepository 9 | aggregate_type: LidskaSila\Prooph\Tests\EventSourcing\FakeImplementations\TestAggregateRoot 10 | aggregate_translator: Prooph\EventSourcing\EventStoreIntegration\AggregateTranslator 11 | one_stream_per_aggregate: true 12 | 13 | event_store: 14 | use: 15 | config: default 16 | factory: Prooph\EventStore\Container\InMemoryEventStoreFactory 17 | default: 18 | plugins: 19 | - Prooph\EventStoreBusBridge\EventPublisher 20 | 21 | services: 22 | - Prooph\EventSourcing\EventStoreIntegration\AggregateTranslator 23 | - Prooph\EventStoreBusBridge\EventPublisher 24 | - Prooph\Common\Messaging\FQCNMessageFactory 25 | -------------------------------------------------------------------------------- /tests/Prooph/AsynchronousMessages/Configurators/AsynchronousEventProducerConfiguratorTest.php: -------------------------------------------------------------------------------- 1 | givenTestContainer(self::TEST_CONFIG); 17 | 18 | $producer = $this->whenGetServiceByNameFromContainer(self::EXPECTED_SERVICE_NAME); 19 | 20 | $this->thenIsInstanceOfExpectedClass(AsynchronousMessageProducer::class, $producer); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/Prooph/AsynchronousMessages/Configurators/AsynchronousQueryProducerConfiguratorTest.php: -------------------------------------------------------------------------------- 1 | givenTestContainer(self::TEST_CONFIG); 17 | 18 | $producer = $this->whenGetServiceByNameFromContainer(self::EXPECTED_SERVICE_NAME); 19 | 20 | $this->thenIsInstanceOfExpectedClass(AsynchronousMessageProducer::class, $producer); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/Prooph/AsynchronousMessages/Configurators/AsynchronousCommandProducerConfiguratorTest.php: -------------------------------------------------------------------------------- 1 | givenTestContainer(self::TEST_CONFIG); 17 | 18 | $producer = $this->whenGetServiceByNameFromContainer(self::EXPECTED_SERVICE_NAME); 19 | 20 | $this->thenIsInstanceOfExpectedClass(AsynchronousMessageProducer::class, $producer); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/Prooph/EventSourcing/FakeImplementations/MemoryTestRepository.php: -------------------------------------------------------------------------------- 1 | eventStore->beginTransaction(); 20 | $this->saveAggregateRoot($testAggregateRoot); 21 | $this->eventStore->commit(); 22 | } 23 | 24 | public function load(UuidInterface $uuid): TestAggregateRoot 25 | { 26 | /** @var TestAggregateRoot $testAggregateRoot */ 27 | $testAggregateRoot = $this->getAggregateRoot($uuid->toString()); 28 | 29 | return $testAggregateRoot; 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/ServiceBus/ServiceBusesConfigurator.php: -------------------------------------------------------------------------------- 1 | extension), 28 | new EventBusConfigurator($this->extension), 29 | new QueryBusConfigurator($this->extension), 30 | ]; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Tom?? ???ek 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /tests/Prooph/AsynchronousMessages/Factories/AbstractAsynchronousMessageProducerFactoryTest.php: -------------------------------------------------------------------------------- 1 | willFailWith(InvalidArgumentException::class); 15 | 16 | $this->whenCallStaticWithoutContainerWrapper(); 17 | } 18 | 19 | private function willFailWith($class) 20 | { 21 | self::expectException(InvalidArgumentException::class); 22 | } 23 | 24 | private function whenCallStaticWithoutContainerWrapper(): void 25 | { 26 | $factoryClass = $this->getFactoryClass(); 27 | $factoryClass::testWithoutContainerWrapper(); 28 | } 29 | 30 | private function getFactoryClass(): string 31 | { 32 | return AbstractAsynchronousMessageProducerFactory::class; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/AsynchronousMessages/AsynchronousMessagesConfigurator.php: -------------------------------------------------------------------------------- 1 | extension), 28 | new AsynchronousEventProducerConfigurator($this->extension), 29 | new AsynchronousQueryProducerConfigurator($this->extension), 30 | ]; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/ProophExtensionConfigurator.php: -------------------------------------------------------------------------------- 1 | extension), 30 | new ProjectionManagerConfigurator($this->extension), 31 | new EventSourcingConfigurator($this->extension), 32 | new ServiceBusesConfigurator($this->extension), 33 | new AsynchronousMessagesConfigurator($this->extension), 34 | ]; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Common/CompositeConfigurator.php: -------------------------------------------------------------------------------- 1 | configurators = $this->createConfigurators(); 17 | } 18 | 19 | /** 20 | * @return Configurator[] 21 | */ 22 | abstract protected function createConfigurators(): array; 23 | 24 | abstract public function getConfigKey(): ?string; 25 | 26 | public function buildDefaultConfig(): array 27 | { 28 | $config = []; 29 | foreach ($this->configurators as $configurator) { 30 | $config[$configurator->getConfigKey()] = $configurator->buildDefaultConfig(); 31 | } 32 | 33 | return $config; 34 | } 35 | 36 | public function loadConfiguration(array $config): void 37 | { 38 | foreach ($this->configurators as $configurator) { 39 | $serviceConfig = $config[$configurator->getConfigKey()]; 40 | $configurator->loadConfiguration($serviceConfig); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/Prooph/configs/ProjectionManagerTests/MysqlProjectionManagerTest.neon: -------------------------------------------------------------------------------- 1 | extensions: 2 | prooph: LidskaSila\Prooph\ProophExtension 3 | 4 | prooph: 5 | event_store: 6 | use: 7 | config: default 8 | factory: Prooph\EventStore\Pdo\Container\MySqlEventStoreFactory 9 | default: 10 | wrap_action_event_emitter: true 11 | plugins: 12 | - Prooph\EventStoreBusBridge\EventPublisher 13 | connection: @fakePDO 14 | persistence_strategy: Prooph\EventStore\Pdo\PersistenceStrategy\MySqlSingleStreamStrategy 15 | load_batch_size: 1000 16 | event_streams_table: event_streams 17 | projection_manager: 18 | use: 19 | config: default 20 | factory: Prooph\EventStore\Pdo\Container\MySqlProjectionManagerFactory 21 | default: 22 | event_store: @prooph.event_store 23 | connection: @fakePDO # service id for the used pdo connection 24 | event_streams_table: event_streams # event stream table to use, defaults to `event_streams` 25 | projections_table: projections # projection table to use, defaults to `projections` 26 | services: 27 | - Prooph\EventStoreBusBridge\EventPublisher 28 | fakePDO: 29 | factory: LidskaSila\Prooph\Tests\ProjectionManager\FakePDOFactory::create() 30 | - Prooph\EventStore\Pdo\PersistenceStrategy\MySqlSingleStreamStrategy 31 | - Prooph\ServiceBus\Plugin\InvokeStrategy\HandleCommandStrategy 32 | - Prooph\Common\Messaging\FQCNMessageFactory 33 | -------------------------------------------------------------------------------- /tests/Prooph/configs/ProjectionManagerTests/MariaDbProjectionManagerTest.neon: -------------------------------------------------------------------------------- 1 | extensions: 2 | prooph: LidskaSila\Prooph\ProophExtension 3 | 4 | prooph: 5 | event_store: 6 | use: 7 | config: default 8 | factory: Prooph\EventStore\Pdo\Container\MariaDbEventStoreFactory 9 | default: 10 | wrap_action_event_emitter: true 11 | plugins: 12 | - Prooph\EventStoreBusBridge\EventPublisher 13 | connection: @fakePDO 14 | persistence_strategy: Prooph\EventStore\Pdo\PersistenceStrategy\MariaDbSingleStreamStrategy 15 | load_batch_size: 1000 16 | event_streams_table: event_streams 17 | 18 | projection_manager: 19 | use: 20 | config: default 21 | factory: Prooph\EventStore\Pdo\Container\MariaDbProjectionManagerFactory 22 | default: 23 | event_store: @prooph.event_store 24 | connection: @fakePDO # service id for the used pdo connection 25 | event_streams_table: event_streams # event stream table to use, defaults to `event_streams` 26 | projections_table: projections # projection table to use, defaults to `projections` 27 | services: 28 | - Prooph\EventStoreBusBridge\EventPublisher 29 | fakePDO: 30 | factory: LidskaSila\Prooph\Tests\ProjectionManager\FakePDOFactory::create() 31 | - Prooph\EventStore\Pdo\PersistenceStrategy\MariaDbSingleStreamStrategy 32 | - Prooph\ServiceBus\Plugin\InvokeStrategy\HandleCommandStrategy 33 | - Prooph\Common\Messaging\FQCNMessageFactory 34 | -------------------------------------------------------------------------------- /tests/Prooph/ProophExtensionTestCase.php: -------------------------------------------------------------------------------- 1 | setTempDirectory(TEMP_DIR); 22 | $config->addConfig(__DIR__ . '/' . self::CONFIGS_DIR . '/' . $configPath); 23 | 24 | return $config; 25 | } 26 | 27 | protected function givenTestContainer($configPath = self::DEFAULT_TEST_FILE) 28 | { 29 | $testConfig = $this->givenTestConfig($configPath); 30 | 31 | $this->container = $testConfig->createContainer(); 32 | } 33 | 34 | protected function whenGetServiceByTypeFromContainer($classType) 35 | { 36 | return $this->container->getByType($classType); 37 | } 38 | 39 | protected function whenGetServiceByNameFromContainer($name) 40 | { 41 | return $this->container->getService($name); 42 | } 43 | 44 | protected function thenIsInstanceOfExpectedClass($expected, $actual): void 45 | { 46 | self::assertInstanceOf($expected, $actual); 47 | } 48 | 49 | protected function thenPass(): void 50 | { 51 | self::assertTrue(true); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/EventStore/EventStoreConfigurator.php: -------------------------------------------------------------------------------- 1 | [ 20 | self::KEY_USE_CONFIG_NAME => [], 21 | self::KEY_USE_FACTORY => false, 22 | ], 23 | ]; 24 | } 25 | 26 | public function getConfigKey(): string 27 | { 28 | return self::KEY; 29 | } 30 | 31 | public function loadConfiguration(array $config): void 32 | { 33 | $eventStoreConfigName = $config[self::KEY_USE][self::KEY_USE_CONFIG_NAME]; 34 | $factory = $config[self::KEY_USE][self::KEY_USE_FACTORY]; 35 | 36 | $containerWrapperServiceId = $this->getContainerWrapperServiceId(); 37 | 38 | $this 39 | ->getContainerBuilder() 40 | ->addDefinition($this->getEventStoreServiceId()) 41 | ->setClass(EventStore::class) 42 | ->setFactory(self::class . '::create', [ $factory, $eventStoreConfigName, '@' . $containerWrapperServiceId ]); 43 | } 44 | 45 | public static function create($factory, $eventStoreConfigName, $containerWrapperService): EventStore 46 | { 47 | return $factory::$eventStoreConfigName($containerWrapperService); 48 | } 49 | 50 | private function getEventStoreServiceId() 51 | { 52 | return $this->extension->prefix('event_store'); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/AsynchronousMessages/Configurators/AbstractAsynchronousProducerConfigurator.php: -------------------------------------------------------------------------------- 1 | getContainerWrapperServiceId(); 32 | 33 | $this 34 | ->getContainerBuilder() 35 | ->addDefinition($this->getProducerServiceId()) 36 | ->setClass(AsynchronousMessageProducer::class) 37 | ->setFactory(static::class . '::create', [ $this->getProducerFactoryClass(), $this->getConfigKey(), '@' . $containerWrapperServiceId ]); 38 | } 39 | 40 | private function getProducerServiceId(): string 41 | { 42 | return $this->extension->prefix(AsynchronousMessagesConfigurator::KEY . '.' . $this->getConfigKey()); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/EventSourcing/EventSourcingConfigurator.php: -------------------------------------------------------------------------------- 1 | [], 20 | ]; 21 | } 22 | 23 | public function getConfigKey(): string 24 | { 25 | return self::KEY; 26 | } 27 | 28 | public function loadConfiguration(array $config): void 29 | { 30 | $repositoriesConfigs = $config[self::KEY_AGGREGATE_REPOSITORIES]; 31 | 32 | $containerWrapperServiceId = $this->getContainerWrapperServiceId(); 33 | 34 | foreach ($repositoriesConfigs as $repositoryConfigName => $repositoryConfig) { 35 | $repositoryClass = $repositoryConfig[self::KEY_REPOSITORY_CLASS]; 36 | $this 37 | ->getContainerBuilder() 38 | ->addDefinition($this->extension->prefix($repositoryConfigName)) 39 | ->setClass($repositoryClass) 40 | ->setFactory(self::class . '::createRepository', [ $repositoryConfigName, '@' . $containerWrapperServiceId ]); 41 | } 42 | } 43 | 44 | public static function createRepository($repositoryConfigName, $containerWrapperService): AggregateRepository 45 | { 46 | return AggregateRepositoryFactory::$repositoryConfigName($containerWrapperService); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/ProjectionManager/ProjectionManagerConfigurator.php: -------------------------------------------------------------------------------- 1 | getContainerWrapperServiceId(); 40 | 41 | $this 42 | ->getContainerBuilder() 43 | ->addDefinition($this->getProjectionManagerServiceId()) 44 | ->setClass(ProjectionManager::class) 45 | ->setFactory(self::class . '::create', [ $factory, $eventStoreConfigName, '@' . $containerWrapperServiceId ]); 46 | } 47 | 48 | private function getProjectionManagerServiceId() 49 | { 50 | return $this->extension->prefix('projection_manager'); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/ServiceBus/ServiceBusConfigurators/AbstractBusConfigurator.php: -------------------------------------------------------------------------------- 1 | [], 26 | self::KEY_BUS_ROUTER => [ 27 | self::KEY_BUS_ROUTER_ROUTES => [], 28 | ], 29 | self::KEY_ENABLE_HANDLER_LOCATION => true, 30 | self::KEY_MESSAGE_FACTORY => MessageFactory::class, 31 | ]; 32 | } 33 | 34 | public function loadConfiguration(array $config): void 35 | { 36 | $containerWrapperServiceId = $this->getContainerWrapperServiceId(); 37 | 38 | $this 39 | ->getContainerBuilder() 40 | ->addDefinition($this->getBusServiceId()) 41 | ->setClass($this->getBusClass()) 42 | ->setFactory(static::class . '::create', [ $this->getBusFactoryClass(), $this->getConfigKey(), '@' . $containerWrapperServiceId ]); 43 | } 44 | 45 | public static function create($factory, $busConfigName, $containerWrapperService): MessageBus 46 | { 47 | return $factory::$busConfigName($containerWrapperService); 48 | } 49 | 50 | private function getBusServiceId(): string 51 | { 52 | return $this->extension->prefix($this->getConfigKey()); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /docs/KeepLearning.md: -------------------------------------------------------------------------------- 1 | # I am new to to prooph 2 | If you are new to prooph, I recommend: 3 |
    4 |
  1. Read Prooph documentation
  2. 5 |
  3. Dive deep in code of example real life application built on top of prooph toolbox.
  4. 6 |
  5. Join to prooph gitter chat
  6. 7 |
8 | 9 | # I am new to whole concept of this weird stuff 10 | If you are new to Domain Driven Design, CQRS or Event Sourcing, I recommend following sources: 11 | 12 | ### YouTube videos 13 | 25 | 26 | ### Online reading 27 | 39 | 40 | ### Books 41 | 42 | 59 | 60 | Or if you are really hungry, 61 | 62 | look at this extended source list 63 | . 64 | -------------------------------------------------------------------------------- /docs/Configuration.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | ## Supported prooph libraries 4 | 11 | 12 | ## Basics 13 |

14 | Array structure for configuration is exactly same as in original prooph libraries, because 15 | extension uses original interop factories from toolbox. 16 | However there is some additional config fields (listed below). 17 |

18 | 19 | ## Example 20 |

21 | Example basic neon configuration can be found here 22 | or you can see other working test configs for inspiration. 23 |

24 | 25 | ## Additional config fields 26 | 27 | ### `event_store` 28 |

29 | There is additional configuration in `event_store` library config. 30 | It has special key `use`, where you need to define which config name and factory should be used: 31 |

32 | 33 | ```yaml 34 | prooph: 35 | event_store: 36 | use: 37 | config: default 38 | factory: Prooph\EventStore\Container\InMemoryEventStoreFactory 39 | ``` 40 | 41 | ### `projection_manager` 42 |

43 | There is additional configuration in `projection_manager` library config. 44 | It has special key `use`, where you need to define which config name and factory should be used: 45 |

46 | 47 | ```yaml 48 | prooph: 49 | projection_manager: 50 | use: 51 | config: default 52 | factory: Prooph\EventStore\Pdo\Container\MySqlProjectionManagerFactory 53 | ``` 54 | -------------------------------------------------------------------------------- /src/Common/NetteContainerWrapper.php: -------------------------------------------------------------------------------- 1 | config = $extensionConfig; 28 | $this->container = $container; 29 | } 30 | 31 | /** 32 | * {@inheritdoc} 33 | */ 34 | public function get($key) 35 | { 36 | try { 37 | return $this->tryToGetByKey($key); 38 | } catch (MissingServiceException $e) { 39 | throw new NotFoundException($e->getMessage()); 40 | } catch (Throwable $e) { 41 | throw new ContainerException($e->getMessage()); 42 | } 43 | } 44 | 45 | /** 46 | * {@inheritdoc} 47 | */ 48 | public function has($key) 49 | { 50 | if ($this->isKeyConfig($key)) { 51 | return (bool) $this->config; 52 | } 53 | if ($this->isServiceId($key)) { 54 | return $this->container->hasService($this->extractServiceId($key)); 55 | } 56 | 57 | return (bool) $this->container->getByType($key); 58 | } 59 | 60 | private function tryToGetByKey($key) 61 | { 62 | if ($this->isKeyConfig($key)) { 63 | return $this->config; 64 | } 65 | if ($this->isServiceId($key)) { 66 | return $this->container->getService($this->extractServiceId($key)); 67 | } 68 | 69 | return $this->container->getByType($key); 70 | } 71 | 72 | private function isKeyConfig(string $key): bool 73 | { 74 | return $key === 'config'; 75 | } 76 | 77 | private function isServiceId(string $key): bool 78 | { 79 | return strpos($key, '@') === 0; 80 | } 81 | 82 | private function extractServiceId(string $key): string 83 | { 84 | return ltrim($key, '@'); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LidskaSila/Prooph 2 | 3 | [![Build Status](https://img.shields.io/travis/LidskaSila/Prooph.svg?style=flat-square)](https://travis-ci.org/LidskaSila/Prooph) 4 | [![Quality Score](https://img.shields.io/scrutinizer/g/LidskaSila/Prooph.svg?style=flat-square)](https://scrutinizer-ci.com/g/LidskaSila/Prooph) 5 | [![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/LidskaSila/Prooph.svg?style=flat-square)](https://scrutinizer-ci.com/g/LidskaSila/Prooph) 6 | 7 | Nette extension for prooph toolbox family. 8 | 9 | ## Why bother? 10 |
    11 |
  1. 12 | It allows you to 13 | 14 | configure prooph libraries through Nette *.neon config 15 | 16 |
  2. 17 |
  3. 18 | It allows you to 19 | 20 | configure routes for asynchronous messaging 21 | with simple bridge interface to adapt your infrastructure. 22 |
  4. 23 |
24 | 25 | 26 | New to Prooph, DDD, CQRS or Event Sourcing? Hunting for inspiration and learning sources? 27 | 28 | 29 | # Quick start 30 | 31 | ### 1) Install this Nette extension through composer 32 | `composer require lidskasila/prooph` 33 | 34 | ### 2) Register package in your config.neon 35 | ```yaml 36 | extensions: 37 | prooph: LidskaSila\Prooph\ProophExtension 38 | ``` 39 | 40 | ## Documentation 41 | 42 |
    43 |
  1. 44 | 45 | Configuration 46 | 47 |
  2. 48 |
  3. 49 | 50 | Asynchronous messaging 51 | 52 |
  4. 53 |
54 | 55 | ## Contribute 56 | 57 | Please feel free to fork and extend existing or add new features and send a pull request with your changes! 58 | To establish a consistent code quality, please provide unit tests for all your changes and may adapt the documentation. 59 | -------------------------------------------------------------------------------- /src/AsynchronousMessages/AsynchronousMessageProducer.php: -------------------------------------------------------------------------------- 1 | producerBridge = $producerBridge; 27 | $this->messageConverter = $messageConverter; 28 | } 29 | 30 | public function injectRoutes(array $routes) 31 | { 32 | $this->routes = $routes; 33 | } 34 | 35 | public function __invoke(Message $message, Deferred $deferred = null): void 36 | { 37 | if ($deferred !== null) { 38 | throw new RuntimeException(__CLASS__ . ' cannot handle query messages which require future responses.'); 39 | } 40 | $data = $this->arrayFromMessage($message); 41 | 42 | $producerName = $this->getProducerRouteKey($message); 43 | 44 | $this->producerBridge->publishWithRoutingKey($producerName, $data); 45 | } 46 | 47 | private function arrayFromMessage(Message $message): array 48 | { 49 | $messageData = $this->messageConverter->convertToArray($message); 50 | MessageDataAssertion::assert($messageData); 51 | $messageData['created_at'] = $message->createdAt()->format('Y-m-d\TH:i:s.u'); 52 | 53 | return $messageData; 54 | } 55 | 56 | private function getProducerRouteKey(Message $message): string 57 | { 58 | if (empty($this->routes[$message->messageName()])) { 59 | throw new RuntimeException( 60 | sprintf( 61 | 'Producer route key for message of name "%s" in asynchronous routing not found.', 62 | $message->messageName() 63 | ) 64 | ); 65 | } 66 | return $this->routes[$message->messageName()]; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /tests/Prooph/configs/FullTestConfig.neon: -------------------------------------------------------------------------------- 1 | extensions: 2 | prooph: LidskaSila\Prooph\ProophExtension 3 | 4 | prooph: 5 | event_sourcing: 6 | aggregate_repository: 7 | test_repository: 8 | repository_class: LidskaSila\Prooph\Tests\EventSourcing\FakeImplementations\MemoryTestRepository 9 | aggregate_type: LidskaSila\Prooph\Tests\EventSourcing\FakeImplementations\TestAggregateRoot 10 | aggregate_translator: Prooph\EventSourcing\EventStoreIntegration\AggregateTranslator 11 | one_stream_per_aggregate: true 12 | event_store: 13 | use: 14 | config: default 15 | factory: Prooph\EventStore\Container\InMemoryEventStoreFactory 16 | default: 17 | plugins: 18 | - Prooph\EventStoreBusBridge\EventPublisher 19 | projection_manager: 20 | use: 21 | config: default 22 | factory: Prooph\EventStore\Pdo\Container\MySqlProjectionManagerFactory 23 | default: 24 | event_store: @prooph.event_store 25 | connection: @fakePDO # service id for the used pdo connection 26 | event_streams_table: event_streams # event stream table to use, defaults to `event_streams` 27 | projections_table: projections # projection table to use, defaults to `projections` 28 | service_bus: 29 | command_bus: 30 | plugins: 31 | - Prooph\ServiceBus\Plugin\InvokeStrategy\HandleCommandStrategy 32 | router: 33 | routes: 34 | event_bus: 35 | plugins: 36 | - Prooph\ServiceBus\Plugin\InvokeStrategy\OnEventStrategy 37 | router: 38 | routes: 39 | async_switch: "@prooph.asynchronous_messaging.events" 40 | query_bus: 41 | plugins: 42 | router: 43 | routes: 44 | asynchronous_messaging: 45 | events: 46 | bridge: "@producerBridge" 47 | routes: 48 | LidskaSila\Prooph\Tests\EventSourcing\FakeImplementations\TestAggregateCreated: producerRouteKey 49 | commands: 50 | bridge: LidskaSila\Prooph\Tests\ServiceBus\AsynchronousMessages\FakeImplementations\TestAsynchronousMessageProducerBridge 51 | 52 | services: 53 | onEventStrategy: Prooph\ServiceBus\Plugin\InvokeStrategy\OnEventStrategy 54 | - Prooph\ServiceBus\Plugin\InvokeStrategy\HandleCommandStrategy 55 | - Prooph\EventStoreBusBridge\EventPublisher 56 | - Prooph\EventSourcing\EventStoreIntegration\AggregateTranslator 57 | - Prooph\Common\Messaging\FQCNMessageFactory 58 | 59 | producerBridge: LidskaSila\Prooph\Tests\AsynchronousMessages\FakeImplementations\TestAsynchronousMessageProducerBridge 60 | - Prooph\Common\Messaging\NoOpMessageConverter 61 | -------------------------------------------------------------------------------- /src/ProophExtension.php: -------------------------------------------------------------------------------- 1 | configurator = new ProophExtensionConfigurator($this); 23 | } 24 | 25 | public function loadConfiguration(): void 26 | { 27 | $this->defaults = $this->configurator->buildDefaultConfig(); 28 | $this->config = $this->mergeConfigIntoDefaults($this->config, $this->defaults); 29 | 30 | $this->registerContainerWrapper(); 31 | 32 | $this->configurator->loadConfiguration($this->config); 33 | } 34 | 35 | private function mergeConfigIntoDefaults($original, $default): array 36 | { 37 | return array_replace_recursive($default, $original); 38 | } 39 | 40 | /** 41 | * Container wrapper is needed in every interop factory, so we register it first. 42 | */ 43 | private function registerContainerWrapper(): void 44 | { 45 | /** @var ContainerBuilder $containerBuilder */ 46 | $containerBuilder = $this->getContainerBuilder(); 47 | 48 | if (!$containerBuilder->getByType(NetteContainerWrapper::class)) { 49 | $jsonConfig = $this->determineJsonConfig(); 50 | $containerServiceId = $this->getContainerServiceId($containerBuilder); 51 | 52 | $containerBuilder 53 | ->addDefinition($this->getNetteContainerWrapperDefinitionId()) 54 | ->setClass(NetteContainerWrapper::class) 55 | ->setFactory(static::class . '::createContainerWrapper', [ $jsonConfig, '@' . $containerServiceId ]); 56 | } 57 | } 58 | 59 | public static function createContainerWrapper(string $jsonConfig, $container): NetteContainerWrapper 60 | { 61 | $config = json_decode($jsonConfig, true); 62 | 63 | return new NetteContainerWrapper($config, $container); 64 | } 65 | 66 | private function getNetteContainerWrapperDefinitionId(): string 67 | { 68 | return $this->prefix('NetteContainerWrapper'); 69 | } 70 | 71 | private function determineJsonConfig(): string 72 | { 73 | // Putting config array to json, because we want keep service links (@serviceId) in config as string. 74 | // If it would be array, Nette would replace all these string based links with real services. 75 | $config = [ $this->configurator->getConfigKey() => $this->config ]; 76 | return json_encode($config); 77 | } 78 | 79 | private function getContainerServiceId(ContainerBuilder $containerBuilder): string 80 | { 81 | return (string) $containerBuilder->getByType(Container::class); 82 | } 83 | } 84 | 85 | -------------------------------------------------------------------------------- /tests/Prooph/Common/NetteContainerWrapperTest.php: -------------------------------------------------------------------------------- 1 | 'value' ]; 14 | 15 | /** @var NetteContainerWrapper */ 16 | protected $containerWrapper; 17 | 18 | public function setUp() 19 | { 20 | parent::setUp(); 21 | $this->givenNetteContainerWrapper('FullTestConfig.neon'); 22 | } 23 | 24 | public function testGet_NotExistingService_ShouldThrowNotFoundException() 25 | { 26 | $this->willThrowException(NotFoundException::class); 27 | 28 | $this->whenGetByKey('not_existing'); 29 | } 30 | 31 | public function testGet_WithInvalidParameter_ShouldThrowContainerException() 32 | { 33 | $this->willThrowException(ContainerException::class); 34 | 35 | $this->whenGetByKey(new \DateTime()); 36 | } 37 | 38 | public function testGet_ExistingServiceByType_ShouldReturnExpectedInstance() 39 | { 40 | $service = $this->whenGetByKey(OnEventStrategy::class); 41 | 42 | $this->thenIsInstanceOfExpectedClass(OnEventStrategy::class, $service); 43 | } 44 | 45 | public function testGet_ExistingServiceById_ShouldReturnExpectedInstance() 46 | { 47 | $service = $this->whenGetByKey('@onEventStrategy'); 48 | 49 | $this->thenIsInstanceOfExpectedClass(OnEventStrategy::class, $service); 50 | } 51 | 52 | public function testGet_Config_ShouldReturnExpectedConfig() 53 | { 54 | $actualConfig = $this->whenGetByKey('config'); 55 | 56 | $this->thenIsExpectedConfig($actualConfig); 57 | } 58 | 59 | public function testHas_ExistingServiceByType_ShouldReturnTrue() 60 | { 61 | $has = $this->whenAskIfHasKey(OnEventStrategy::class); 62 | 63 | $this->thenResultIsTrue($has); 64 | } 65 | 66 | public function testHas_ExistingServiceById_ShouldReturnTrue() 67 | { 68 | $has = $this->whenAskIfHasKey('@onEventStrategy'); 69 | 70 | $this->thenResultIsTrue($has); 71 | } 72 | 73 | public function testHas_Config_ShouldReturnTrue() 74 | { 75 | $has = $this->whenAskIfHasKey('config'); 76 | 77 | $this->thenResultIsTrue($has); 78 | } 79 | 80 | private function givenNetteContainerWrapper(string $configName) 81 | { 82 | $this->givenTestContainer($configName); 83 | $config = $this->givenFakeConfig(); 84 | $this->containerWrapper = new NetteContainerWrapper($config, $this->container); 85 | } 86 | 87 | private function givenFakeConfig() 88 | { 89 | return self::FAKE_CONFIG; 90 | } 91 | 92 | private function whenGetByKey($key) 93 | { 94 | return $this->containerWrapper->get($key); 95 | } 96 | 97 | private function whenAskIfHasKey($key) 98 | { 99 | return $this->containerWrapper->has($key); 100 | } 101 | 102 | private function thenIsExpectedConfig($actualConfig) 103 | { 104 | self::assertEquals(self::FAKE_CONFIG, $actualConfig); 105 | } 106 | 107 | private function willThrowException($class) 108 | { 109 | self::expectException($class); 110 | } 111 | 112 | private function thenResultIsTrue($has) 113 | { 114 | self::assertTrue($has); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/AsynchronousMessages/Factories/AbstractAsynchronousMessageProducerFactory.php: -------------------------------------------------------------------------------- 1 | configId = $configId; 29 | } 30 | 31 | /** 32 | * Creates a new instance from a specified config, specifically meant to be used as static factory. 33 | * 34 | * @throws InvalidArgumentException 35 | */ 36 | public static function __callStatic(string $name, array $arguments): AsynchronousMessageProducer 37 | { 38 | if (!isset($arguments[0]) || !$arguments[0] instanceof ContainerInterface) { 39 | throw new InvalidArgumentException( 40 | sprintf('The first argument must be of type %s', ContainerInterface::class) 41 | ); 42 | } 43 | 44 | return (new static($name))->__invoke($arguments[0]); 45 | } 46 | 47 | public function __invoke(ContainerInterface $container): AsynchronousMessageProducer 48 | { 49 | $producerConfig = $this->getProducerconfg($container); 50 | 51 | $producer = $this->createMessageProducer($container, $producerConfig); 52 | 53 | $this->injectRoutesToProducer($producerConfig, $producer); 54 | 55 | return $producer; 56 | } 57 | 58 | public function dimensions(): iterable 59 | { 60 | return [ 'prooph', 'asynchronous_messaging' ]; 61 | } 62 | 63 | public function defaultOptions(): iterable 64 | { 65 | return []; 66 | } 67 | 68 | private function getProducerconfg(ContainerInterface $container): array 69 | { 70 | $config = $container->get('config'); 71 | 72 | return $this->optionsWithFallback($config, $this->configId); 73 | } 74 | 75 | private function createMessageProducer(ContainerInterface $container, array $producerConfig): AsynchronousMessageProducer 76 | { 77 | $producerBridge = $this->getProducerBridge($container, $producerConfig); 78 | $messageConverter = $this->getMessageConverter($container); 79 | 80 | return new AsynchronousMessageProducer($producerBridge, $messageConverter); 81 | } 82 | 83 | private function getProducerBridge(ContainerInterface $container, $producerConfig): AsynchronousMessageProducerBridge 84 | { 85 | $producerBridgeKey = $this->getProducerBridgeServiceKey($producerConfig); 86 | 87 | return $container->get($producerBridgeKey); 88 | } 89 | 90 | private function getProducerBridgeServiceKey(array $producerConfig): string 91 | { 92 | return $producerConfig[self::KEY_BRIDGE]; 93 | } 94 | 95 | private function getMessageConverter(ContainerInterface $container): MessageConverter 96 | { 97 | return $container->get(MessageConverter::class); 98 | } 99 | 100 | private function injectRoutesToProducer(array $producerConfig, AsynchronousMessageProducer $producer): void 101 | { 102 | $routes = is_array($producerConfig[self::KEY_ROUTES]) ? $producerConfig[self::KEY_ROUTES] : []; 103 | $producer->injectRoutes($routes); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /tests/Prooph/AsynchronousMessages/AsynchronousMessageProducerTest.php: -------------------------------------------------------------------------------- 1 | givenTestProducerBridge(); 31 | $this->givenAsynchronousMessageProducer(); 32 | } 33 | 34 | public function testInvoke_WithDeferredParam_ShouldThrowRuntimeException() 35 | { 36 | $testMessage = $this->givenTestMessage(); 37 | $deffered = $this->givenMockedDeffered(); 38 | 39 | $this->willFailWith(RuntimeException::class); 40 | 41 | $this->whenInvokeProducerWith($testMessage, $deffered); 42 | } 43 | 44 | public function testInvoke_WithoutRoutes_ShouldThrowRuntimeException() 45 | { 46 | $testMessage = $this->givenTestMessage(); 47 | 48 | $this->willFailWith(RuntimeException::class); 49 | 50 | $this->whenInvokeProducerWith($testMessage); 51 | } 52 | 53 | public function testInvoke_WithProperRoute_ShouldPublishExpectedMessageToExpectedProducerRouteKey() 54 | { 55 | $this->givenTestProducerHasInjectedTestRoute(); 56 | 57 | $testMessage = $this->givenTestMessage(); 58 | 59 | $this->whenInvokeProducerWith($testMessage); 60 | 61 | $this->thenShouldPublishExpectedMessageToExpectedProducerRouteKey(self::TEST_PRODUCER_ROUTE_KEY); 62 | } 63 | 64 | private function givenTestProducerBridge(): void 65 | { 66 | $this->testProducerBridge = new TestAsynchronousMessageProducerBridge(); 67 | } 68 | 69 | private function givenAsynchronousMessageProducer(): void 70 | { 71 | $messageConverter = new NoOpMessageConverter(); 72 | $this->testProducer = new AsynchronousMessageProducer($this->testProducerBridge, $messageConverter); 73 | } 74 | 75 | private function givenMockedDeffered(): Deferred 76 | { 77 | $deffered = Mockery::mock('React\Promise\Deferred'); 78 | assert($deffered instanceof Deferred); 79 | 80 | return $deffered; 81 | } 82 | 83 | private function givenTestProducerHasInjectedTestRoute() 84 | { 85 | $this->testProducer->injectRoutes( 86 | [ 87 | TestAggregateCreated::class => self::TEST_PRODUCER_ROUTE_KEY, 88 | ] 89 | ); 90 | } 91 | 92 | private function givenTestMessage(): Message 93 | { 94 | return TestAggregateCreated::create(Uuid::uuid4()); 95 | } 96 | 97 | private function willFailWith($class) 98 | { 99 | self::expectException($class); 100 | } 101 | 102 | private function whenInvokeProducerWith(Message $message, Deferred $deferred = null): void 103 | { 104 | $producer = $this->testProducer; 105 | $producer($message, $deferred); 106 | } 107 | 108 | private function thenShouldPublishExpectedMessageToExpectedProducerRouteKey($expectedProducerRouteKey): void 109 | { 110 | $publishedMessagesDump = $this->getPublishedEventsFromTestBridge(); 111 | 112 | self::assertCount(1, $publishedMessagesDump); 113 | $publishedProducerRouteKey = $publishedMessagesDump[0][TestAsynchronousMessageProducerBridge::KEY_ROUTING_KEY]; 114 | $publishedMessageData = $publishedMessagesDump[0][TestAsynchronousMessageProducerBridge::KEY_DATA]; 115 | self::assertEquals($publishedProducerRouteKey, $expectedProducerRouteKey); 116 | self::assertEquals($publishedMessageData['message_name'], TestAggregateCreated::class); 117 | self::assertEquals($publishedMessageData['payload'], TestAggregateCreated::TEST_PAYLOAD); 118 | } 119 | 120 | private function getPublishedEventsFromTestBridge(): array 121 | { 122 | return $this->testProducerBridge->getPublished(); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /docs/AsynchronousMessaging.md: -------------------------------------------------------------------------------- 1 | # AsynchronousMessaging 2 | 3 |

4 | If you want integrate prooph with some asynchronous messaging library 5 | of your choice (for example Kdyby/RabbitMq), 6 | you need to write whole implementation of `Prooph\ServiceBus\Async\MessageProducer` on your own. 7 | And you need to make it so that you can configure what message to what producer/exchange should go, right? 8 | But hang on boy. 9 |

10 | 11 | ## This package is shipped with configurable MessageProducer 12 |

13 | Implemented with 14 | `LidskaSila\Prooph\AsynchronousMessages\AsynchronousMessageProducer`, 15 | to make it little bit easier for you. It is automatically 16 | registered in Nette container with special id for each bus (if you provide 17 | special config for it). 18 |

19 | 20 |

21 | In this config (example for asynchronous events configuration): 22 |

23 | 24 | ```yaml 25 | prooph: 26 | asynchronous_messaging: 27 | events: 28 | bridge: "@producerBridge" 29 | routes: 30 | LidskaSila\Prooph\Tests\EventSourcing\FakeImplementations\TestAggregateCreated: producerRouteKey 31 | ``` 32 | 33 |

34 | you set your routes and type/id of your service that implements bridge interface 35 | (`LidskaSila\Prooph\AsynchronousMessages\AsynchronousMessageProducerBridge`) 36 | (Yes, damn, you still have to implement something but I will show you, it's easy!). 37 |

38 | 39 |

40 | This will register service `LidskaSila\Prooph\AsynchronousMessages\AsynchronousMessageProducer` 41 | with container id "prooph.asynchronous_messaging.events". 42 |

43 | 44 |

45 | But to make it work, you need to have registered two services: 46 |

47 |
    48 |
  1. 49 | Your implementation of `LidskaSila\Prooph\AsynchronousMessages\AsynchronousMessageProducerBridge` 50 | with ID you provided in asynchronous_messaging config (you can have it type based). 51 |
  2. 52 |
  3. 53 | Some `Prooph\Common\Messaging\MessageConverter` implementation - it is strategy of how 54 | your Message will be converted to array. 55 | There is default NoOpMessageConverter or you can implement your own. ;) 56 |
  4. 57 |
58 | 59 | For example: 60 | 61 | ```yaml 62 | services: 63 | producerBridge: LidskaSila\Prooph\Tests\AsynchronousMessages\FakeImplementations\TestAsynchronousMessageProducerBridge 64 | - Prooph\Common\Messaging\NoOpMessageConverter 65 | ``` 66 | 67 |
68 | 69 | Then, you can set async_switch router at Command, Event and/or Query bus configuration with 70 | that special id. To stick to our example: 71 | ```yaml 72 | prooph: 73 | service_bus: 74 | event_bus: 75 | router: 76 | async_switch: "@prooph.asynchronous_messaging.events" 77 | ``` 78 | 79 | ## Final working example config might be: 80 | 81 | ```yaml 82 | prooph: 83 | asynchronous_messaging: 84 | events: 85 | bridge: "@producerBridge" 86 | routes: 87 | LidskaSila\Prooph\Tests\EventSourcing\FakeImplementations\TestAggregateCreated: producerRouteKey 88 | service_bus: 89 | event_bus: 90 | router: 91 | async_switch: "@prooph.asynchronous_messaging.events" 92 | 93 | services: 94 | producerBridge: LidskaSila\Prooph\Tests\AsynchronousMessages\FakeImplementations\TestAsynchronousMessageProducerBridge 95 | - Prooph\Common\Messaging\NoOpMessageConverter 96 | - Prooph\Common\Messaging\FQCNMessageFactory 97 | ``` 98 | 99 |

100 | Now, your messages emmited from bus will go through your implementation of bridge you set. 101 |

102 | 103 |

104 | Note: messages are routed based on message-name parameter, not class name! 105 | It's just that by default message-name parameter equals message class name. 106 |

107 | 108 |
109 | 110 | ## Example `AsynchronousMessageProducerBridge` implementation 111 |

112 | Implementing `LidskaSila\Prooph\AsynchronousMessages\AsynchronousMessageProducerBridge` is very easy, 113 | it has just one method: 114 |

115 | 116 | ```php 117 | 129 | So in case of Kdyby/Rabbit, for example, it can be implemented as easy as: 130 |

131 | 132 | 133 | ```php 134 | rabbit = $rabbit; 150 | } 151 | 152 | public function publishWithRoutingKey($producerName, $data): void 153 | { 154 | $jsonData = json_encode($data); 155 | $this->rabbit->getProducer($producerName)->publish($jsonData, $producerName); 156 | } 157 | } 158 | 159 | ``` 160 | --------------------------------------------------------------------------------