├── .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 | - Read Prooph documentation
5 | - Dive deep in code of example real life application built on top of prooph toolbox.
6 | - Join to prooph gitter chat
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 | [](https://travis-ci.org/LidskaSila/Prooph)
4 | [](https://scrutinizer-ci.com/g/LidskaSila/Prooph)
5 | [](https://scrutinizer-ci.com/g/LidskaSila/Prooph)
6 |
7 | Nette extension for prooph toolbox family.
8 |
9 | ## Why bother?
10 |
11 | -
12 | It allows you to
13 |
14 | configure prooph libraries through Nette *.neon config
15 |
16 |
17 | -
18 | It allows you to
19 |
20 | configure routes for asynchronous messaging
21 | with simple bridge interface to adapt your infrastructure.
22 |
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 | -
44 |
45 | Configuration
46 |
47 |
48 | -
49 |
50 | Asynchronous messaging
51 |
52 |
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 | -
49 | Your implementation of `LidskaSila\Prooph\AsynchronousMessages\AsynchronousMessageProducerBridge`
50 | with ID you provided in asynchronous_messaging config (you can have it type based).
51 |
52 | -
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 |
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 |
--------------------------------------------------------------------------------