├── .coveralls.yml ├── .docheader ├── .gitignore ├── .php_cs ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── composer.json ├── docs ├── bookdown.json ├── causation_metadata_enricher.md ├── event_publisher.md └── transaction_manager.md ├── phpunit.xml.dist ├── src ├── CausationMetadataEnricher.php ├── Container │ ├── EventPublisherFactory.php │ └── TransactionManagerFactory.php ├── EventPublisher.php ├── Exception │ ├── EventStoreBusBridgeException.php │ └── InvalidArgumentException.php └── TransactionManager.php └── tests ├── CausationMetadataEnricherTest.php ├── Container ├── EventPublisherFactoryTest.php └── TransactionManagerFactoryTest.php ├── EventPublisherTest.php └── TransactionManagerTest.php /.coveralls.yml: -------------------------------------------------------------------------------- 1 | # for php-coveralls 2 | service_name: travis-ci 3 | src_dir: src 4 | coverage_clover: build/logs/clover.xml 5 | -------------------------------------------------------------------------------- /.docheader: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of prooph/event-store-bus-bridge. 3 | * (c) 2014-%year% Alexander Miertsch 4 | * (c) 2015-%year% Sascha-Oliver Prolic 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.settings 2 | /.project 3 | /.buildpath 4 | /vendor 5 | .idea 6 | .php_cs.cache 7 | nbproject 8 | composer.lock 9 | docs/html 10 | .phpunit.result.cache 11 | -------------------------------------------------------------------------------- /.php_cs: -------------------------------------------------------------------------------- 1 | getFinder()->in(__DIR__); 5 | 6 | $cacheDir = getenv('TRAVIS') ? getenv('HOME') . '/.php-cs-fixer' : __DIR__; 7 | 8 | $config->setCacheFile($cacheDir . '/.php_cs.cache'); 9 | 10 | return $config; 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | matrix: 4 | fast_finish: true 5 | include: 6 | - php: 7.3 7 | env: 8 | - DEPENDENCIES="" 9 | - EXECUTE_CS_CHECK=true 10 | - TEST_COVERAGE=true 11 | - php: 7.3 12 | env: 13 | - DEPENDENCIES="--prefer-lowest --prefer-stable" 14 | - php: 7.4 15 | env: 16 | - DEPENDENCIES="" 17 | - php: 7.4 18 | env: 19 | - DEPENDENCIES="--prefer-lowest --prefer-stable" 20 | - php: 8.0 21 | env: 22 | - DEPENDENCIES="" 23 | - php: 8.0 24 | env: 25 | - DEPENDENCIES="--prefer-lowest --prefer-stable" 26 | 27 | cache: 28 | directories: 29 | - $HOME/.composer/cache 30 | - $HOME/.php-cs-fixer 31 | - $HOME/.local 32 | 33 | before_script: 34 | - mkdir -p "$HOME/.php-cs-fixer" 35 | - phpenv config-rm xdebug.ini 36 | - composer self-update 37 | - composer update $DEPENDENCIES 38 | 39 | script: 40 | - if [[ $TEST_COVERAGE == 'true' ]]; then XDEBUG_MODE=coverage php -dzend_extension=xdebug.so ./vendor/bin/phpunit --coverage-text --coverage-clover ./build/logs/clover.xml; else ./vendor/bin/phpunit; fi 41 | - if [[ $EXECUTE_CS_CHECK == 'true' ]]; then ./vendor/bin/php-cs-fixer fix -v --diff --dry-run; fi 42 | 43 | after_success: 44 | - if [[ $TEST_COVERAGE == 'true' ]]; then php vendor/bin/coveralls -v; fi 45 | 46 | notifications: 47 | webhooks: 48 | urls: 49 | - https://webhooks.gitter.im/e/61c75218816eebde4486 50 | on_success: change # options: [always|never|change] default: always 51 | on_failure: always # options: [always|never|change] default: always 52 | on_start: never # options: [always|never|change] default: always 53 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [v3.4.0](https://github.com/prooph/event-store-bus-bridge/tree/v3.4.0) 4 | 5 | [Full Changelog](https://github.com/prooph/event-store-bus-bridge/compare/v3.3.0...v3.4.0) 6 | 7 | **Merged pull requests:** 8 | 9 | - Php8 compability [\#43](https://github.com/prooph/event-store-bus-bridge/pull/43) ([fritz-gerneth](https://github.com/fritz-gerneth)) 10 | - Change copyright [\#42](https://github.com/prooph/event-store-bus-bridge/pull/42) ([codeliner](https://github.com/codeliner)) 11 | - Update cs headers [\#41](https://github.com/prooph/event-store-bus-bridge/pull/41) ([basz](https://github.com/basz)) 12 | 13 | ## [v3.3.0](https://github.com/prooph/event-store-bus-bridge/tree/v3.3.0) (2018-08-06) 14 | 15 | [Full Changelog](https://github.com/prooph/event-store-bus-bridge/compare/v3.2.0...v3.3.0) 16 | 17 | **Implemented enhancements:** 18 | 19 | - Support for configurable causation metadata key [\#39](https://github.com/prooph/event-store-bus-bridge/issues/39) 20 | - Made causation metadata keys configurable [\#40](https://github.com/prooph/event-store-bus-bridge/pull/40) ([pkruithof](https://github.com/pkruithof)) 21 | 22 | ## [v3.2.0](https://github.com/prooph/event-store-bus-bridge/tree/v3.2.0) (2018-04-04) 23 | 24 | [Full Changelog](https://github.com/prooph/event-store-bus-bridge/compare/v3.1.0...v3.2.0) 25 | 26 | **Implemented enhancements:** 27 | 28 | - Dispatch EVENT\_APPEND\_TO and EVENT\_CREATE in transactional event store when not inside transaction [\#38](https://github.com/prooph/event-store-bus-bridge/pull/38) ([destebang](https://github.com/destebang)) 29 | 30 | **Merged pull requests:** 31 | 32 | - doc block [\#37](https://github.com/prooph/event-store-bus-bridge/pull/37) ([basz](https://github.com/basz)) 33 | 34 | ## [v3.1.0](https://github.com/prooph/event-store-bus-bridge/tree/v3.1.0) (2017-12-17) 35 | 36 | [Full Changelog](https://github.com/prooph/event-store-bus-bridge/compare/v3.0.2...v3.1.0) 37 | 38 | **Implemented enhancements:** 39 | 40 | - test php 7.2 on travis [\#36](https://github.com/prooph/event-store-bus-bridge/pull/36) ([prolic](https://github.com/prolic)) 41 | 42 | **Closed issues:** 43 | 44 | - 3.0.2 Release is a BC-Break [\#35](https://github.com/prooph/event-store-bus-bridge/issues/35) 45 | 46 | ## [v3.0.2](https://github.com/prooph/event-store-bus-bridge/tree/v3.0.2) (2017-09-25) 47 | 48 | [Full Changelog](https://github.com/prooph/event-store-bus-bridge/compare/v3.0.1...v3.0.2) 49 | 50 | **Fixed bugs:** 51 | 52 | - fix causation meta data enricher docs, remove useless factory [\#34](https://github.com/prooph/event-store-bus-bridge/pull/34) ([prolic](https://github.com/prolic)) 53 | 54 | **Closed issues:** 55 | 56 | - CausationMetadataEnricher not invoked when command bus is created before event store [\#32](https://github.com/prooph/event-store-bus-bridge/issues/32) 57 | 58 | ## [v3.0.1](https://github.com/prooph/event-store-bus-bridge/tree/v3.0.1) (2017-07-08) 59 | 60 | [Full Changelog](https://github.com/prooph/event-store-bus-bridge/compare/v3.0.0...v3.0.1) 61 | 62 | **Fixed bugs:** 63 | 64 | - EventPublisher should not publish events if event contains errors / e… [\#31](https://github.com/prooph/event-store-bus-bridge/pull/31) ([prolic](https://github.com/prolic)) 65 | 66 | **Closed issues:** 67 | 68 | - EventPublisher should not publish events if event contains errors / exceptions [\#30](https://github.com/prooph/event-store-bus-bridge/issues/30) 69 | - TransactionManager breaks on non found aggregates [\#29](https://github.com/prooph/event-store-bus-bridge/issues/29) 70 | 71 | ## [v3.0.0](https://github.com/prooph/event-store-bus-bridge/tree/v3.0.0) (2017-03-30) 72 | 73 | [Full Changelog](https://github.com/prooph/event-store-bus-bridge/compare/v3.0.0-beta2...v3.0.0) 74 | 75 | **Implemented enhancements:** 76 | 77 | - small fix in event publisher [\#19](https://github.com/prooph/event-store-bus-bridge/pull/19) ([prolic](https://github.com/prolic)) 78 | 79 | **Merged pull requests:** 80 | 81 | - Composer [\#28](https://github.com/prooph/event-store-bus-bridge/pull/28) ([basz](https://github.com/basz)) 82 | - update to use psr\container [\#27](https://github.com/prooph/event-store-bus-bridge/pull/27) ([basz](https://github.com/basz)) 83 | 84 | ## [v3.0.0-beta2](https://github.com/prooph/event-store-bus-bridge/tree/v3.0.0-beta2) (2017-01-12) 85 | 86 | [Full Changelog](https://github.com/prooph/event-store-bus-bridge/compare/v3.0.0-beta1...v3.0.0-beta2) 87 | 88 | **Implemented enhancements:** 89 | 90 | - improve causation metadata enricher [\#25](https://github.com/prooph/event-store-bus-bridge/pull/25) ([prolic](https://github.com/prolic)) 91 | - update plugin registration [\#24](https://github.com/prooph/event-store-bus-bridge/pull/24) ([prolic](https://github.com/prolic)) 92 | 93 | **Merged pull requests:** 94 | 95 | - Travis config improvement [\#26](https://github.com/prooph/event-store-bus-bridge/pull/26) ([oqq](https://github.com/oqq)) 96 | 97 | ## [v3.0.0-beta1](https://github.com/prooph/event-store-bus-bridge/tree/v3.0.0-beta1) (2016-12-13) 98 | 99 | [Full Changelog](https://github.com/prooph/event-store-bus-bridge/compare/v2.0...v3.0.0-beta1) 100 | 101 | **Implemented enhancements:** 102 | 103 | - Move causation info setting to MetadataEnricherPlugin [\#18](https://github.com/prooph/event-store-bus-bridge/issues/18) 104 | - update for event store changes [\#23](https://github.com/prooph/event-store-bus-bridge/pull/23) ([prolic](https://github.com/prolic)) 105 | - Updates [\#21](https://github.com/prooph/event-store-bus-bridge/pull/21) ([prolic](https://github.com/prolic)) 106 | - Support for PHP 7.1 [\#20](https://github.com/prooph/event-store-bus-bridge/pull/20) ([prolic](https://github.com/prolic)) 107 | 108 | **Closed issues:** 109 | 110 | - Update to coveralls ^1.0 [\#17](https://github.com/prooph/event-store-bus-bridge/issues/17) 111 | 112 | **Merged pull requests:** 113 | 114 | - Origin/improvement/interface names [\#22](https://github.com/prooph/event-store-bus-bridge/pull/22) ([basz](https://github.com/basz)) 115 | 116 | ## [v2.0](https://github.com/prooph/event-store-bus-bridge/tree/v2.0) (2015-11-22) 117 | 118 | [Full Changelog](https://github.com/prooph/event-store-bus-bridge/compare/v2.0-beta.2...v2.0) 119 | 120 | **Implemented enhancements:** 121 | 122 | - attach transaction manager on invoke, not on initialize [\#12](https://github.com/prooph/event-store-bus-bridge/pull/12) ([prolic](https://github.com/prolic)) 123 | - update composer.json [\#8](https://github.com/prooph/event-store-bus-bridge/pull/8) ([prolic](https://github.com/prolic)) 124 | 125 | **Merged pull requests:** 126 | 127 | - v2.0 [\#16](https://github.com/prooph/event-store-bus-bridge/pull/16) ([codeliner](https://github.com/codeliner)) 128 | - Update docs to support bookdown [\#15](https://github.com/prooph/event-store-bus-bridge/pull/15) ([codeliner](https://github.com/codeliner)) 129 | - updated bookdown templates to version 0.2.0 [\#14](https://github.com/prooph/event-store-bus-bridge/pull/14) ([sandrokeil](https://github.com/sandrokeil)) 130 | - added bookdown.io documentation [\#13](https://github.com/prooph/event-store-bus-bridge/pull/13) ([sandrokeil](https://github.com/sandrokeil)) 131 | - Convert transaction manager to event store plugin [\#11](https://github.com/prooph/event-store-bus-bridge/pull/11) ([codeliner](https://github.com/codeliner)) 132 | - event store 6.0-beta dep [\#9](https://github.com/prooph/event-store-bus-bridge/pull/9) ([codeliner](https://github.com/codeliner)) 133 | - Check if event store is in transaction [\#6](https://github.com/prooph/event-store-bus-bridge/pull/6) ([codeliner](https://github.com/codeliner)) 134 | 135 | ## [v2.0-beta.2](https://github.com/prooph/event-store-bus-bridge/tree/v2.0-beta.2) (2015-10-22) 136 | 137 | [Full Changelog](https://github.com/prooph/event-store-bus-bridge/compare/v2.0-beta.1...v2.0-beta.2) 138 | 139 | **Merged pull requests:** 140 | 141 | - Update event store service id [\#10](https://github.com/prooph/event-store-bus-bridge/pull/10) ([codeliner](https://github.com/codeliner)) 142 | 143 | ## [v2.0-beta.1](https://github.com/prooph/event-store-bus-bridge/tree/v2.0-beta.1) (2015-10-21) 144 | 145 | [Full Changelog](https://github.com/prooph/event-store-bus-bridge/compare/v1.1...v2.0-beta.1) 146 | 147 | **Fixed bugs:** 148 | 149 | - Only rollback transaction if in active transaction [\#5](https://github.com/prooph/event-store-bus-bridge/issues/5) 150 | 151 | **Merged pull requests:** 152 | 153 | - Support prooph/event-store v6 [\#7](https://github.com/prooph/event-store-bus-bridge/pull/7) ([codeliner](https://github.com/codeliner)) 154 | 155 | ## [v1.1](https://github.com/prooph/event-store-bus-bridge/tree/v1.1) (2015-09-19) 156 | 157 | [Full Changelog](https://github.com/prooph/event-store-bus-bridge/compare/v1.0...v1.1) 158 | 159 | ## [v1.0](https://github.com/prooph/event-store-bus-bridge/tree/v1.0) (2015-09-08) 160 | 161 | [Full Changelog](https://github.com/prooph/event-store-bus-bridge/compare/v0.1...v1.0) 162 | 163 | ## [v0.1](https://github.com/prooph/event-store-bus-bridge/tree/v0.1) (2015-08-31) 164 | 165 | [Full Changelog](https://github.com/prooph/event-store-bus-bridge/compare/e1d7a9ee3f1015f6a98f59c064fe504a6562dc2c...v0.1) 166 | 167 | **Implemented enhancements:** 168 | 169 | - Add tests [\#4](https://github.com/prooph/event-store-bus-bridge/pull/4) ([prolic](https://github.com/prolic)) 170 | 171 | **Merged pull requests:** 172 | 173 | - Fix Readme [\#3](https://github.com/prooph/event-store-bus-bridge/pull/3) ([prolic](https://github.com/prolic)) 174 | - Add event publisher [\#2](https://github.com/prooph/event-store-bus-bridge/pull/2) ([codeliner](https://github.com/codeliner)) 175 | - Add transaction manager [\#1](https://github.com/prooph/event-store-bus-bridge/pull/1) ([codeliner](https://github.com/codeliner)) 176 | 177 | 178 | 179 | \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* 180 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2021, Alexander Miertsch 2 | Copyright (c) 2015-2021, Sascha-Oliver Prolic 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | * Neither the name of prooph nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Prooph Event Store Bus Bridge 2 | 3 | Marry CQRS with Event Sourcing 4 | 5 | [![Build Status](https://travis-ci.com/prooph/event-store-bus-bridge.svg?branch=master)](https://travis-ci.com/prooph/event-store-bus-bridge) 6 | [![Coverage Status](https://coveralls.io/repos/prooph/event-store-bus-bridge/badge.svg?branch=master&service=github)](https://coveralls.io/github/prooph/event-store-bus-bridge?branch=master) 7 | [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/prooph/improoph) 8 | 9 | This package acts as a glue component between [prooph/service-bus](https://github.com/prooph/service-bus) and [prooph/event-store](https://github.com/prooph/event-store). 10 | 11 | ## Important 12 | 13 | This library will receive support until December 31, 2019 and will then be deprecated. 14 | 15 | For further information see the official announcement here: [https://www.sasaprolic.com/2018/08/the-future-of-prooph-components.html](https://www.sasaprolic.com/2018/08/the-future-of-prooph-components.html) 16 | 17 | ## Features 18 | 19 | - [Transaction handling](docs/transaction_manager.md) based on command dispatch 20 | - [Event publishing](docs/event_publisher.md) after event store commit 21 | - [Causation Metadata Enricher](docs/causation_metadata_enricher.md) based on command dispatch & event-store create/appendTo 22 | 23 | ## Documentation 24 | 25 | Documentation is [in the doc tree](docs/), and can be compiled using [bookdown](http://bookdown.io). 26 | 27 | ```console 28 | $ php ./vendor/bin/bookdown docs/bookdown.json 29 | $ php -S 0.0.0.0:8080 -t docs/html/ 30 | ``` 31 | 32 | Then browse to [http://localhost:8080/](http://localhost:8080/) 33 | 34 | ## Support 35 | 36 | - Ask questions on Stack Overflow tagged with [#prooph](https://stackoverflow.com/questions/tagged/prooph). 37 | - File issues at [https://github.com/prooph/event-store-bus-bridge/issues](https://github.com/prooph/event-store-bus-bridge/issues). 38 | - Say hello in the [prooph gitter](https://gitter.im/prooph/improoph) chat. 39 | 40 | ## Contribute 41 | 42 | Please feel free to fork and extend existing or add new plugins and send a pull request with your changes! 43 | To establish a consistent code quality, please provide unit tests for all your changes and may adapt the documentation. 44 | 45 | ## Dependencies 46 | 47 | Please refer to the project [composer.json](composer.json) for the list of dependencies. 48 | 49 | ## License 50 | 51 | Released under the [New BSD License](LICENSE). 52 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "prooph/event-store-bus-bridge", 3 | "description": "Marry CQRS with Event Sourcing", 4 | "type": "library", 5 | "license": "BSD-3-Clause", 6 | "homepage": "http://getprooph.org/", 7 | "authors": [ 8 | { 9 | "name": "Alexander Miertsch", 10 | "email": "kontakt@codeliner.ws" 11 | }, 12 | { 13 | "name": "Sascha-Oliver Prolic", 14 | "email": "saschaprolic@googlemail.com" 15 | } 16 | ], 17 | "keywords": [ 18 | "CQRS", 19 | "EventSourcing", 20 | "prooph" 21 | ], 22 | "minimum-stability": "dev", 23 | "prefer-stable": true, 24 | "require": { 25 | "php": "^7.3 | ^8.0", 26 | "prooph/common" : "^4.2.1", 27 | "prooph/service-bus" : "^6.3", 28 | "prooph/event-store" : "^7.3.1" 29 | }, 30 | "require-dev": { 31 | "psr/container": "^1.0", 32 | "phpunit/phpunit": "^9.3", 33 | "phpspec/prophecy": "^1.9", 34 | "phpspec/prophecy-phpunit": "^2.0", 35 | "prooph/php-cs-fixer-config": "^0.4", 36 | "satooshi/php-coveralls": "^1.0", 37 | "prooph/bookdown-template": "^0.2.3" 38 | }, 39 | "extra": { 40 | "branch-alias": { 41 | "dev-master": "3.2-dev" 42 | } 43 | }, 44 | "autoload": { 45 | "psr-4": { 46 | "Prooph\\EventStoreBusBridge\\": "src/" 47 | } 48 | }, 49 | "autoload-dev": { 50 | "psr-4": { 51 | "ProophTest\\EventStoreBusBridge\\": "tests/", 52 | "ProophTest\\ServiceBus\\": "vendor/prooph/service-bus/tests/" 53 | } 54 | }, 55 | "config": { 56 | "preferred-install": { 57 | "prooph/*": "source" 58 | } 59 | }, 60 | "scripts": { 61 | "check": [ 62 | "@cs", 63 | "@test" 64 | ], 65 | "cs": "php-cs-fixer fix -v --diff --dry-run", 66 | "cs-fix": "php-cs-fixer fix -v --diff", 67 | "test": "phpunit" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /docs/bookdown.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Marry CQRS with Event Sourcing", 3 | "content": [ 4 | {"intro": "../README.md"}, 5 | {"event_publisher": "event_publisher.md"}, 6 | {"transaction_manager": "transaction_manager.md"}, 7 | {"causation_metadata_enricher": "causation_metadata_enricher.md"} 8 | ], 9 | "target": "./html", 10 | "template": "../vendor/prooph/bookdown-template/templates/main.php" 11 | } 12 | -------------------------------------------------------------------------------- /docs/causation_metadata_enricher.md: -------------------------------------------------------------------------------- 1 | # Causation Metadata Enricher 2 | 3 | If the `dispatched command` is an instance of `Prooph\Common\Messaging\Message` the transaction manager will also add `causation metadata` to each recorded event during the transaction. 4 | Two entries are added to the metadata: 5 | - `causation_id` = `$command->uuid()->toString()` 6 | - `causation_name` = `$command->messageName()`. 7 | 8 | ## Set Up 9 | 10 | The `CausationMetadataEnricher` is a dual plugin, it's a plugin for event-store as well as for the message-bus. 11 | 12 | ```php 13 | $causationMetadataEnricher = new CausationMetadataEnricher(); 14 | $causationMetadataEnricher->attachToEventStore($eventStore); 15 | $causationMetadataEnricher->attachToMessageBus($commandBus); 16 | ``` 17 | 18 | That's it! 19 | 20 | ### Container-Driven Set Up 21 | 22 | If you are using the `container-aware factories` shipped with prooph/event-store and prooph/service-bus you may also 23 | want to auto register the `CausationMetadataEnricher`. 24 | 25 | Add the `CausationMetadataEnricher` as plugin to the command bus as well as the event-store. 26 | Keep in mind, that this service must be shared in order to work (which it is by default in most container implementations). 27 | -------------------------------------------------------------------------------- /docs/event_publisher.md: -------------------------------------------------------------------------------- 1 | # Event Publishing 2 | 3 | The `Prooph\EventStoreBusBridge\EventPublisher` is an event store plugin which listens on the event store `create`, 4 | `appendTo` and `commit` action event. 5 | It iterates over the `recordedEvents` and publishes them on the `Prooph\ServiceBus\EventBus`. 6 | 7 | ## Set Up 8 | 9 | The EventPublisher requires an instance of `Prooph\ServiceBus\EventBus` at construction time. 10 | Furthermore, the publisher implements `Prooph\EventStore\Plugin\Plugin`. So you need to pass the event store to the 11 | `EventPublisher::attachToEventStore` method. That's it. From this moment on all domain events are published on the event bus when 12 | an event store transaction is committed. 13 | 14 | ```php 15 | $eventPublisher->attachToEventStore($eventStore); 16 | ``` 17 | 18 | ### Container-Driven Set Up 19 | 20 | If you are using the `container-aware factory` shipped with prooph/event-store you may also 21 | want to auto register the `EventPublisher`. First you need to make the event publisher available as a service in the 22 | container. You can use the `Prooph\EventStoreBusBridge\Container\EventPublisherFactory` for that. 23 | 24 | Map the factory to a service name like `prooph.event_publisher` and add this service name to the list of event store plugins 25 | in your application configuration. Also have a look at the event store docs for more details about the plugin system. 26 | -------------------------------------------------------------------------------- /docs/transaction_manager.md: -------------------------------------------------------------------------------- 1 | # Transaction Handling 2 | 3 | The transaction manager starts a new event store transaction on every command dispatch and commits it afterwards. 4 | 5 | ## Set Up 6 | 7 | To enable transaction handling based on command dispatch you need to set up the `Prooph\EventStoreBusBridge\TransactionManager`. 8 | The transaction manager acts as command bus plugin: 9 | 10 | ```php 11 | /** @var $eventStore Prooph\EventStore\EventStore */ 12 | $transactionManager = new TransactionManager($eventStore); 13 | 14 | /** @var $commandBus Prooph\ServiceBus\CommandBus */ 15 | $transactionManager->attachToMessageBus($commandBus); 16 | ``` 17 | 18 | That's it! 19 | 20 | ### Container-Driven Set Up 21 | 22 | If you are using the `container-aware factories` shipped with prooph/service-bus you may also 23 | want to auto register the `TransactionManager`. As long as the command bus is available as service `Prooph\ServiceBus\CommandBus` in the container you can use 24 | the `Prooph\EventStoreBusBridge\Container\TransactionManagerFactory` for that. Just map the factory to a service name like `prooph.transaction_manager` and 25 | add the service name to the plugin list of the event store configuration. Also have a look at the event store docs for more details about the plugin system. 26 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | 14 | ./tests 15 | 16 | 17 | 18 | 19 | ./src/ 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/CausationMetadataEnricher.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2015-2021 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Prooph\EventStoreBusBridge; 15 | 16 | use ArrayIterator; 17 | use Assert\Assertion; 18 | use Prooph\Common\Event\ActionEvent; 19 | use Prooph\Common\Messaging\Message; 20 | use Prooph\EventStore\ActionEventEmitterEventStore; 21 | use Prooph\EventStore\Metadata\MetadataEnricher; 22 | use Prooph\EventStore\Plugin\Plugin as EventStorePlugin; 23 | use Prooph\EventStore\Stream; 24 | use Prooph\ServiceBus\CommandBus; 25 | use Prooph\ServiceBus\MessageBus; 26 | use Prooph\ServiceBus\Plugin\Plugin as MessageBusPlugin; 27 | 28 | final class CausationMetadataEnricher implements MetadataEnricher, EventStorePlugin, MessageBusPlugin 29 | { 30 | /** 31 | * @var string 32 | */ 33 | private $causationIdKey; 34 | 35 | /** 36 | * @var string 37 | */ 38 | private $causationNameKey; 39 | 40 | /** 41 | * @var Message 42 | */ 43 | private $currentCommand; 44 | 45 | /** 46 | * @var array 47 | */ 48 | private $eventStoreListeners = []; 49 | 50 | /** 51 | * @var array 52 | */ 53 | private $messageBusListeners = []; 54 | 55 | /** 56 | * @param string $causationIdKey 57 | * @param string $causationNameKey 58 | */ 59 | public function __construct(string $causationIdKey = '_causation_id', string $causationNameKey = '_causation_name') 60 | { 61 | Assertion::notEmpty($causationIdKey); 62 | Assertion::notEmpty($causationNameKey); 63 | 64 | $this->causationIdKey = $causationIdKey; 65 | $this->causationNameKey = $causationNameKey; 66 | } 67 | 68 | public function attachToEventStore(ActionEventEmitterEventStore $eventStore): void 69 | { 70 | $this->eventStoreListeners[] = $eventStore->attach( 71 | ActionEventEmitterEventStore::EVENT_APPEND_TO, 72 | function (ActionEvent $event): void { 73 | if (! $this->currentCommand instanceof Message) { 74 | return; 75 | } 76 | 77 | $recordedEvents = $event->getParam('streamEvents'); 78 | 79 | $enrichedRecordedEvents = []; 80 | 81 | foreach ($recordedEvents as $recordedEvent) { 82 | $enrichedRecordedEvents[] = $this->enrich($recordedEvent); 83 | } 84 | 85 | $event->setParam('streamEvents', new ArrayIterator($enrichedRecordedEvents)); 86 | }, 87 | 1000 88 | ); 89 | 90 | $this->eventStoreListeners[] = $eventStore->attach( 91 | ActionEventEmitterEventStore::EVENT_CREATE, 92 | function (ActionEvent $event): void { 93 | if (! $this->currentCommand instanceof Message) { 94 | return; 95 | } 96 | 97 | $stream = $event->getParam('stream'); 98 | $recordedEvents = $stream->streamEvents(); 99 | 100 | $enrichedRecordedEvents = []; 101 | 102 | foreach ($recordedEvents as $recordedEvent) { 103 | $enrichedRecordedEvents[] = $this->enrich($recordedEvent); 104 | } 105 | 106 | $stream = new Stream( 107 | $stream->streamName(), 108 | new ArrayIterator($enrichedRecordedEvents), 109 | $stream->metadata() 110 | ); 111 | 112 | $event->setParam('stream', $stream); 113 | }, 114 | 1000 115 | ); 116 | } 117 | 118 | public function detachFromEventStore(ActionEventEmitterEventStore $eventStore): void 119 | { 120 | foreach ($this->eventStoreListeners as $listenerHandler) { 121 | $eventStore->detach($listenerHandler); 122 | } 123 | 124 | $this->eventStoreListeners = []; 125 | } 126 | 127 | public function attachToMessageBus(MessageBus $messageBus): void 128 | { 129 | $this->messageBusListeners[] = $messageBus->attach( 130 | CommandBus::EVENT_DISPATCH, 131 | function (ActionEvent $event): void { 132 | $this->currentCommand = $event->getParam(CommandBus::EVENT_PARAM_MESSAGE); 133 | }, 134 | CommandBus::PRIORITY_INVOKE_HANDLER + 1000 135 | ); 136 | 137 | $this->messageBusListeners[] = $messageBus->attach( 138 | CommandBus::EVENT_FINALIZE, 139 | function (ActionEvent $event): void { 140 | $this->currentCommand = null; 141 | }, 142 | 1000 143 | ); 144 | } 145 | 146 | public function detachFromMessageBus(MessageBus $messageBus): void 147 | { 148 | foreach ($this->messageBusListeners as $listenerHandler) { 149 | $messageBus->detach($listenerHandler); 150 | } 151 | 152 | $this->messageBusListeners = []; 153 | } 154 | 155 | public function enrich(Message $message): Message 156 | { 157 | $message = $message->withAddedMetadata($this->causationIdKey, $this->currentCommand->uuid()->toString()); 158 | $message = $message->withAddedMetadata($this->causationNameKey, $this->currentCommand->messageName()); 159 | 160 | return $message; 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/Container/EventPublisherFactory.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2015-2021 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Prooph\EventStoreBusBridge\Container; 15 | 16 | use Prooph\EventStoreBusBridge\EventPublisher; 17 | use Prooph\EventStoreBusBridge\Exception\InvalidArgumentException; 18 | use Prooph\ServiceBus\EventBus; 19 | use Psr\Container\ContainerInterface; 20 | 21 | final class EventPublisherFactory 22 | { 23 | /** 24 | * @var string 25 | */ 26 | private $eventBusServiceName; 27 | 28 | /** 29 | * Creates a new instance from a specified config, specifically meant to be used as static factory. 30 | * 31 | * In case you want to use another config key than provided by the factories, you can add the following factory to 32 | * your config: 33 | * 34 | * 35 | * [EventPublisherFactory::class, 'event_bus_service_name'], 38 | * ]; 39 | * 40 | * 41 | * @throws InvalidArgumentException 42 | */ 43 | public static function __callStatic(string $name, array $arguments): EventPublisher 44 | { 45 | if (! isset($arguments[0]) || ! $arguments[0] instanceof ContainerInterface) { 46 | throw new InvalidArgumentException( 47 | \sprintf('The first argument must be of type %s', ContainerInterface::class) 48 | ); 49 | } 50 | 51 | return (new static($name))->__invoke($arguments[0]); 52 | } 53 | 54 | public function __construct(string $eventBusServiceName = EventBus::class) 55 | { 56 | $this->eventBusServiceName = $eventBusServiceName; 57 | } 58 | 59 | public function __invoke(ContainerInterface $container): EventPublisher 60 | { 61 | $eventBus = $container->get($this->eventBusServiceName); 62 | 63 | return new EventPublisher($eventBus); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Container/TransactionManagerFactory.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2015-2021 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Prooph\EventStoreBusBridge\Container; 15 | 16 | use Prooph\EventStore\EventStore; 17 | use Prooph\EventStoreBusBridge\Exception\InvalidArgumentException; 18 | use Prooph\EventStoreBusBridge\TransactionManager; 19 | use Psr\Container\ContainerInterface; 20 | 21 | final class TransactionManagerFactory 22 | { 23 | /** 24 | * @var string 25 | */ 26 | private $eventStoreServiceName; 27 | 28 | /** 29 | * Creates a new instance from a specified config, specifically meant to be used as static factory. 30 | * 31 | * In case you want to use another config key than provided by the factories, you can add the following factory to 32 | * your config: 33 | * 34 | * 35 | * [TransactionManagerFactory::class, 'event_store_service_name'], 38 | * ]; 39 | * 40 | * 41 | * @throws InvalidArgumentException 42 | */ 43 | public static function __callStatic(string $name, array $arguments): TransactionManager 44 | { 45 | if (! isset($arguments[0]) || ! $arguments[0] instanceof ContainerInterface) { 46 | throw new InvalidArgumentException( 47 | \sprintf('The first argument must be of type %s', ContainerInterface::class) 48 | ); 49 | } 50 | 51 | return (new static($name))->__invoke($arguments[0]); 52 | } 53 | 54 | public function __construct(string $eventStoreServiceName = EventStore::class) 55 | { 56 | $this->eventStoreServiceName = $eventStoreServiceName; 57 | } 58 | 59 | public function __invoke(ContainerInterface $container): TransactionManager 60 | { 61 | return new TransactionManager($container->get($this->eventStoreServiceName)); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/EventPublisher.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2015-2021 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Prooph\EventStoreBusBridge; 15 | 16 | use Prooph\Common\Event\ActionEvent; 17 | use Prooph\EventStore\ActionEventEmitterEventStore; 18 | use Prooph\EventStore\EventStore; 19 | use Prooph\EventStore\Plugin\AbstractPlugin; 20 | use Prooph\EventStore\TransactionalActionEventEmitterEventStore; 21 | use Prooph\ServiceBus\EventBus; 22 | 23 | final class EventPublisher extends AbstractPlugin 24 | { 25 | /** 26 | * @var EventBus 27 | */ 28 | private $eventBus; 29 | 30 | /** 31 | * @var \Iterator[] 32 | */ 33 | private $cachedEventStreams = []; 34 | 35 | public function __construct(EventBus $eventBus) 36 | { 37 | $this->eventBus = $eventBus; 38 | } 39 | 40 | public function attachToEventStore(ActionEventEmitterEventStore $eventStore): void 41 | { 42 | $this->listenerHandlers[] = $eventStore->attach( 43 | ActionEventEmitterEventStore::EVENT_APPEND_TO, 44 | function (ActionEvent $event) use ($eventStore): void { 45 | $recordedEvents = $event->getParam('streamEvents', new \ArrayIterator()); 46 | 47 | if (! $this->inTransaction($eventStore)) { 48 | if ($event->getParam('streamNotFound', false) 49 | || $event->getParam('concurrencyException', false) 50 | ) { 51 | return; 52 | } 53 | 54 | foreach ($recordedEvents as $recordedEvent) { 55 | $this->eventBus->dispatch($recordedEvent); 56 | } 57 | } else { 58 | $this->cachedEventStreams[] = $recordedEvents; 59 | } 60 | } 61 | ); 62 | 63 | $this->listenerHandlers[] = $eventStore->attach( 64 | ActionEventEmitterEventStore::EVENT_CREATE, 65 | function (ActionEvent $event) use ($eventStore): void { 66 | $stream = $event->getParam('stream'); 67 | $recordedEvents = $stream->streamEvents(); 68 | 69 | if (! $this->inTransaction($eventStore)) { 70 | if ($event->getParam('streamExistsAlready', false)) { 71 | return; 72 | } 73 | 74 | foreach ($recordedEvents as $recordedEvent) { 75 | $this->eventBus->dispatch($recordedEvent); 76 | } 77 | } else { 78 | $this->cachedEventStreams[] = $recordedEvents; 79 | } 80 | } 81 | ); 82 | 83 | if ($eventStore instanceof TransactionalActionEventEmitterEventStore) { 84 | $this->listenerHandlers[] = $eventStore->attach( 85 | TransactionalActionEventEmitterEventStore::EVENT_COMMIT, 86 | function (ActionEvent $event): void { 87 | foreach ($this->cachedEventStreams as $stream) { 88 | foreach ($stream as $recordedEvent) { 89 | $this->eventBus->dispatch($recordedEvent); 90 | } 91 | } 92 | $this->cachedEventStreams = []; 93 | } 94 | ); 95 | 96 | $this->listenerHandlers[] = $eventStore->attach( 97 | TransactionalActionEventEmitterEventStore::EVENT_ROLLBACK, 98 | function (ActionEvent $event): void { 99 | $this->cachedEventStreams = []; 100 | } 101 | ); 102 | } 103 | } 104 | 105 | private function inTransaction(EventStore $eventStore): bool 106 | { 107 | return $eventStore instanceof TransactionalActionEventEmitterEventStore 108 | && $eventStore->inTransaction(); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/Exception/EventStoreBusBridgeException.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2015-2021 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Prooph\EventStoreBusBridge\Exception; 15 | 16 | interface EventStoreBusBridgeException 17 | { 18 | } 19 | -------------------------------------------------------------------------------- /src/Exception/InvalidArgumentException.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2015-2021 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Prooph\EventStoreBusBridge\Exception; 15 | 16 | class InvalidArgumentException extends \InvalidArgumentException implements EventStoreBusBridgeException 17 | { 18 | } 19 | -------------------------------------------------------------------------------- /src/TransactionManager.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2015-2021 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Prooph\EventStoreBusBridge; 15 | 16 | use Prooph\Common\Event\ActionEvent; 17 | use Prooph\EventStore\TransactionalEventStore; 18 | use Prooph\ServiceBus\CommandBus; 19 | use Prooph\ServiceBus\MessageBus; 20 | use Prooph\ServiceBus\Plugin\AbstractPlugin; 21 | 22 | /** 23 | * The transaction manager starts a new transaction when a command is dispatched on the command bus. 24 | * If the command dispatch finishes without an error the transaction manager commits the transaction otherwise it does a rollback. 25 | * Furthermore it attaches a listener to the event store create.pre and appendTo.pre action events with a low priority to 26 | * set causation_id as metadata for all domain events which are going to be persisted. 27 | */ 28 | final class TransactionManager extends AbstractPlugin 29 | { 30 | /** 31 | * @var TransactionalEventStore 32 | */ 33 | private $eventStore; 34 | 35 | public function __construct(TransactionalEventStore $eventStore) 36 | { 37 | $this->eventStore = $eventStore; 38 | } 39 | 40 | public function attachToMessageBus(MessageBus $messageBus): void 41 | { 42 | $this->listenerHandlers[] = $messageBus->attach( 43 | CommandBus::EVENT_DISPATCH, 44 | function (ActionEvent $event): void { 45 | $this->eventStore->beginTransaction(); 46 | }, 47 | CommandBus::PRIORITY_INVOKE_HANDLER + 1000 48 | ); 49 | 50 | $this->listenerHandlers[] = $messageBus->attach( 51 | CommandBus::EVENT_FINALIZE, 52 | function (ActionEvent $event): void { 53 | if ($this->eventStore->inTransaction()) { 54 | if ($event->getParam(CommandBus::EVENT_PARAM_EXCEPTION)) { 55 | $this->eventStore->rollback(); 56 | } else { 57 | $this->eventStore->commit(); 58 | } 59 | } 60 | }, 61 | 1000 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tests/CausationMetadataEnricherTest.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2015-2021 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace ProophTest\EventStoreBusBridge; 15 | 16 | use Assert\InvalidArgumentException; 17 | use PHPUnit\Framework\TestCase; 18 | use Prooph\Common\Event\ActionEvent; 19 | use Prooph\Common\Event\ProophActionEventEmitter; 20 | use Prooph\Common\Messaging\Message; 21 | use Prooph\EventStore\ActionEventEmitterEventStore; 22 | use Prooph\EventStore\EventStore; 23 | use Prooph\EventStore\InMemoryEventStore; 24 | use Prooph\EventStore\Stream; 25 | use Prooph\EventStore\StreamName; 26 | use Prooph\EventStoreBusBridge\CausationMetadataEnricher; 27 | use Prooph\ServiceBus\CommandBus; 28 | use Prooph\ServiceBus\Plugin\Router\CommandRouter; 29 | use ProophTest\ServiceBus\Mock\DoSomething; 30 | use ProophTest\ServiceBus\Mock\SomethingDone; 31 | 32 | class CausationMetadataEnricherTest extends TestCase 33 | { 34 | /** 35 | * @test 36 | */ 37 | public function it_enriches_command_on_create_stream(): void 38 | { 39 | $eventStore = $this->getEventStore(); 40 | 41 | $causationMetadataEnricher = new CausationMetadataEnricher(); 42 | 43 | $causationMetadataEnricher->attachToEventStore($eventStore); 44 | 45 | $commandBus = new CommandBus(); 46 | $router = new CommandRouter(); 47 | $router->route(DoSomething::class)->to(function (DoSomething $command) use ($eventStore): void { 48 | /* @var EventStore $eventStore */ 49 | $eventStore->create( 50 | new Stream( 51 | new StreamName('something'), 52 | new \ArrayIterator([ 53 | new SomethingDone(['name' => $command->payload('name')]), 54 | ]) 55 | ) 56 | ); 57 | }); 58 | 59 | $result = null; 60 | 61 | $eventStore->attach( 62 | ActionEventEmitterEventStore::EVENT_CREATE, 63 | function (ActionEvent $event) use (&$result): void { 64 | $stream = $event->getParam('stream'); 65 | $stream->streamEvents()->rewind(); 66 | $result = $stream->streamEvents()->current(); 67 | }, 68 | -1000 69 | ); 70 | 71 | $router->attachToMessageBus($commandBus); 72 | $causationMetadataEnricher->attachToMessageBus($commandBus); 73 | 74 | $command = new DoSomething(['name' => 'Alex'], 1); 75 | 76 | $commandBus->dispatch($command); 77 | 78 | $this->assertArrayHasKey('_causation_id', $result->metadata()); 79 | $this->assertArrayHasKey('_causation_name', $result->metadata()); 80 | 81 | $this->assertEquals($command->uuid()->toString(), $result->metadata()['_causation_id']); 82 | $this->assertEquals(\get_class($command), $result->metadata()['_causation_name']); 83 | } 84 | 85 | /** 86 | * @test 87 | */ 88 | public function it_enriches_command_on_append_to_stream(): void 89 | { 90 | $eventStore = $this->getEventStore(); 91 | 92 | $eventStore->create( 93 | new Stream( 94 | new StreamName('something'), 95 | new \ArrayIterator() 96 | ) 97 | ); 98 | 99 | $causationMetadataEnricher = new CausationMetadataEnricher(); 100 | 101 | $causationMetadataEnricher->attachToEventStore($eventStore); 102 | 103 | $commandBus = new CommandBus(); 104 | $router = new CommandRouter(); 105 | $router->route(DoSomething::class)->to(function (DoSomething $command) use ($eventStore): void { 106 | /* @var EventStore $eventStore */ 107 | $eventStore->appendTo( 108 | new StreamName('something'), 109 | new \ArrayIterator([ 110 | new SomethingDone(['name' => $command->payload('name')]), 111 | ]) 112 | ); 113 | }); 114 | 115 | $result = null; 116 | 117 | $eventStore->attach( 118 | ActionEventEmitterEventStore::EVENT_APPEND_TO, 119 | function (ActionEvent $event) use (&$result): void { 120 | $streamEvents = $event->getParam('streamEvents'); 121 | $streamEvents->rewind(); 122 | $result = $streamEvents->current(); 123 | }, 124 | -1000 125 | ); 126 | 127 | $router->attachToMessageBus($commandBus); 128 | $causationMetadataEnricher->attachToMessageBus($commandBus); 129 | 130 | $command = new DoSomething(['name' => 'Alex'], 1); 131 | 132 | $commandBus->dispatch($command); 133 | 134 | $this->assertArrayHasKey('_causation_id', $result->metadata()); 135 | $this->assertArrayHasKey('_causation_name', $result->metadata()); 136 | 137 | $this->assertEquals($command->uuid()->toString(), $result->metadata()['_causation_id']); 138 | $this->assertEquals(\get_class($command), $result->metadata()['_causation_name']); 139 | } 140 | 141 | /** 142 | * @test 143 | */ 144 | public function it_returns_early_if_command_is_null_on_create_stream(): void 145 | { 146 | $eventStore = $this->getEventStore(); 147 | 148 | $causationMetadataEnricher = new CausationMetadataEnricher(); 149 | 150 | $causationMetadataEnricher->attachToEventStore($eventStore); 151 | 152 | $commandBus = new CommandBus(); 153 | $router = new CommandRouter(); 154 | $router->route(DoSomething::class)->to(function (DoSomething $command) use ($eventStore): void { 155 | /* @var EventStore $eventStore */ 156 | $eventStore->create( 157 | new Stream( 158 | new StreamName('something'), 159 | new \ArrayIterator([ 160 | new SomethingDone(['name' => $command->payload('name')]), 161 | ]) 162 | ) 163 | ); 164 | }); 165 | 166 | $result = null; 167 | 168 | $eventStore->attach( 169 | ActionEventEmitterEventStore::EVENT_CREATE, 170 | function (ActionEvent $event) use (&$result): void { 171 | $stream = $event->getParam('stream'); 172 | $stream->streamEvents()->rewind(); 173 | $result = $stream->streamEvents()->current(); 174 | }, 175 | -1000 176 | ); 177 | 178 | $router->attachToMessageBus($commandBus); 179 | $causationMetadataEnricher->attachToMessageBus($commandBus); 180 | 181 | $command = new DoSomething(['name' => 'Alex'], 1); 182 | 183 | $commandBus->attach( 184 | CommandBus::EVENT_DISPATCH, 185 | function (ActionEvent $event): void { 186 | $event->setParam(CommandBus::EVENT_PARAM_MESSAGE, null); 187 | }, 188 | CommandBus::PRIORITY_INVOKE_HANDLER + 2000 189 | ); 190 | 191 | $commandBus->attach( 192 | CommandBus::EVENT_DISPATCH, 193 | function (ActionEvent $event) use ($command): void { 194 | $event->setParam(CommandBus::EVENT_PARAM_MESSAGE, $command); 195 | }, 196 | CommandBus::PRIORITY_INVOKE_HANDLER + 500 197 | ); 198 | 199 | $commandBus->dispatch($command); 200 | 201 | $this->assertArrayNotHasKey('_causation_id', $result->metadata()); 202 | $this->assertArrayNotHasKey('_causation_name', $result->metadata()); 203 | } 204 | 205 | /** 206 | * @test 207 | */ 208 | public function it_detaches_from_command_bus_and_event_store(): void 209 | { 210 | $eventStore = $this->getEventStore(); 211 | 212 | $causationMetadataEnricher = new CausationMetadataEnricher(); 213 | 214 | $causationMetadataEnricher->attachToEventStore($eventStore); 215 | 216 | $commandBus = new CommandBus(); 217 | $router = new CommandRouter(); 218 | $router->route(DoSomething::class)->to(function (DoSomething $command) use ($eventStore): void { 219 | /* @var EventStore $eventStore */ 220 | $eventStore->create( 221 | new Stream( 222 | new StreamName('something'), 223 | new \ArrayIterator([ 224 | new SomethingDone(['name' => $command->payload('name')]), 225 | ]) 226 | ) 227 | ); 228 | }); 229 | 230 | $result = null; 231 | 232 | $eventStore->attach( 233 | ActionEventEmitterEventStore::EVENT_CREATE, 234 | function (ActionEvent $event) use (&$result): void { 235 | $stream = $event->getParam('stream'); 236 | $stream->streamEvents()->rewind(); 237 | $result = $stream->streamEvents()->current(); 238 | }, 239 | -1000 240 | ); 241 | 242 | $router->attachToMessageBus($commandBus); 243 | $causationMetadataEnricher->attachToMessageBus($commandBus); 244 | 245 | $causationMetadataEnricher->detachFromEventStore($eventStore); 246 | $causationMetadataEnricher->detachFromMessageBus($commandBus); 247 | 248 | $command = new DoSomething(['name' => 'Alex']); 249 | 250 | $commandBus->dispatch($command); 251 | 252 | $this->assertArrayNotHasKey('_causation_id', $result->metadata()); 253 | $this->assertArrayNotHasKey('_causation_name', $result->metadata()); 254 | } 255 | 256 | /** 257 | * @test 258 | */ 259 | public function it_returns_early_if_command_is_null_on_append_to_stream(): void 260 | { 261 | $eventStore = $this->getEventStore(); 262 | 263 | $eventStore->create( 264 | new Stream( 265 | new StreamName('something'), 266 | new \ArrayIterator() 267 | ) 268 | ); 269 | 270 | $causationMetadataEnricher = new CausationMetadataEnricher(); 271 | 272 | $causationMetadataEnricher->attachToEventStore($eventStore); 273 | 274 | $commandBus = new CommandBus(); 275 | $router = new CommandRouter(); 276 | $router->route(DoSomething::class)->to(function (DoSomething $command) use ($eventStore): void { 277 | /* @var EventStore $eventStore */ 278 | $eventStore->appendTo( 279 | new StreamName('something'), 280 | new \ArrayIterator([ 281 | new SomethingDone(['name' => $command->payload('name')]), 282 | ]) 283 | ); 284 | }); 285 | 286 | $result = null; 287 | 288 | $eventStore->attach( 289 | ActionEventEmitterEventStore::EVENT_APPEND_TO, 290 | function (ActionEvent $event) use (&$result): void { 291 | $streamEvents = $event->getParam('streamEvents'); 292 | $streamEvents->rewind(); 293 | $result = $streamEvents->current(); 294 | }, 295 | -1000 296 | ); 297 | 298 | $router->attachToMessageBus($commandBus); 299 | $causationMetadataEnricher->attachToMessageBus($commandBus); 300 | 301 | $command = new DoSomething(['name' => 'Alex'], 1); 302 | 303 | $commandBus->attach( 304 | CommandBus::EVENT_DISPATCH, 305 | function (ActionEvent $event): void { 306 | $event->setParam(CommandBus::EVENT_PARAM_MESSAGE, null); 307 | }, 308 | CommandBus::PRIORITY_INVOKE_HANDLER + 2000 309 | ); 310 | 311 | $commandBus->attach( 312 | CommandBus::EVENT_DISPATCH, 313 | function (ActionEvent $event) use ($command): void { 314 | $event->setParam(CommandBus::EVENT_PARAM_MESSAGE, $command); 315 | }, 316 | CommandBus::PRIORITY_INVOKE_HANDLER + 500 317 | ); 318 | 319 | $commandBus->dispatch($command); 320 | 321 | $this->assertArrayNotHasKey('_causation_id', $result->metadata()); 322 | $this->assertArrayNotHasKey('_causation_name', $result->metadata()); 323 | } 324 | 325 | /** 326 | * @test 327 | */ 328 | public function it_returns_early_if_command_is_no_instance_of_message_on_create_stream(): void 329 | { 330 | $eventStore = $this->getEventStore(); 331 | 332 | $causationMetadataEnricher = new CausationMetadataEnricher(); 333 | 334 | $causationMetadataEnricher->attachToEventStore($eventStore); 335 | 336 | $commandBus = new CommandBus(); 337 | $router = new CommandRouter(); 338 | $router->route('do something')->to(function (string $command) use ($eventStore): void { 339 | /* @var EventStore $eventStore */ 340 | $eventStore->create( 341 | new Stream( 342 | new StreamName('something'), 343 | new \ArrayIterator([ 344 | new SomethingDone(['foo' => 'bar']), 345 | ]) 346 | ) 347 | ); 348 | }); 349 | 350 | $result = null; 351 | 352 | $eventStore->attach( 353 | ActionEventEmitterEventStore::EVENT_CREATE, 354 | function (ActionEvent $event) use (&$result): void { 355 | $stream = $event->getParam('stream'); 356 | $stream->streamEvents()->rewind(); 357 | $result = $stream->streamEvents()->current(); 358 | }, 359 | -1000 360 | ); 361 | 362 | $router->attachToMessageBus($commandBus); 363 | $causationMetadataEnricher->attachToMessageBus($commandBus); 364 | 365 | $command = 'do something'; 366 | 367 | $commandBus->attach( 368 | CommandBus::EVENT_DISPATCH, 369 | function (ActionEvent $event) use ($command): void { 370 | $event->setParam(CommandBus::EVENT_PARAM_MESSAGE, $command); 371 | }, 372 | CommandBus::PRIORITY_INVOKE_HANDLER + 500 373 | ); 374 | 375 | $commandBus->dispatch($command); 376 | 377 | $this->assertInstanceOf(Message::class, $result); 378 | $this->assertArrayNotHasKey('_causation_id', $result->metadata()); 379 | $this->assertArrayNotHasKey('_causation_', $result->metadata()); 380 | } 381 | 382 | /** 383 | * @test 384 | */ 385 | public function it_returns_early_if_command_is_no_instance_of_message_on_append_to_stream(): void 386 | { 387 | $eventStore = $this->getEventStore(); 388 | 389 | $eventStore->create( 390 | new Stream( 391 | new StreamName('something'), 392 | new \ArrayIterator() 393 | ) 394 | ); 395 | 396 | $causationMetadataEnricher = new CausationMetadataEnricher(); 397 | 398 | $causationMetadataEnricher->attachToEventStore($eventStore); 399 | 400 | $commandBus = new CommandBus(); 401 | $router = new CommandRouter(); 402 | $router->route('do something')->to(function (string $command) use ($eventStore): void { 403 | /* @var EventStore $eventStore */ 404 | $eventStore->appendTo( 405 | new StreamName('something'), 406 | new \ArrayIterator([ 407 | new SomethingDone(['foo' => 'bar']), 408 | ]) 409 | ); 410 | }); 411 | 412 | $result = null; 413 | 414 | $eventStore->attach( 415 | ActionEventEmitterEventStore::EVENT_APPEND_TO, 416 | function (ActionEvent $event) use (&$result): void { 417 | $streamEvents = $event->getParam('streamEvents'); 418 | $streamEvents->rewind(); 419 | $result = $streamEvents->current(); 420 | }, 421 | -1000 422 | ); 423 | 424 | $router->attachToMessageBus($commandBus); 425 | $causationMetadataEnricher->attachToMessageBus($commandBus); 426 | 427 | $command = 'do something'; 428 | 429 | $commandBus->attach( 430 | CommandBus::EVENT_DISPATCH, 431 | function (ActionEvent $event) use ($command): void { 432 | $event->setParam(CommandBus::EVENT_PARAM_MESSAGE, $command); 433 | }, 434 | CommandBus::PRIORITY_INVOKE_HANDLER + 500 435 | ); 436 | 437 | $commandBus->dispatch($command); 438 | 439 | $this->assertInstanceOf(Message::class, $result); 440 | $this->assertArrayNotHasKey('_causation_id', $result->metadata()); 441 | $this->assertArrayNotHasKey('_causation_', $result->metadata()); 442 | } 443 | 444 | /** 445 | * @test 446 | */ 447 | public function it_has_configurable_metadata_keys(): void 448 | { 449 | $eventStore = $this->getEventStore(); 450 | 451 | $idKey = '$causationId'; 452 | $nameKey = '$causationName'; 453 | $causationMetadataEnricher = new CausationMetadataEnricher($idKey, $nameKey); 454 | $causationMetadataEnricher->attachToEventStore($eventStore); 455 | 456 | $commandBus = new CommandBus(); 457 | $router = new CommandRouter(); 458 | $router->route(DoSomething::class)->to(function (DoSomething $command) use ($eventStore): void { 459 | /* @var EventStore $eventStore */ 460 | $eventStore->create( 461 | new Stream( 462 | new StreamName('something'), 463 | new \ArrayIterator([ 464 | new SomethingDone(['name' => $command->payload('name')]), 465 | ]) 466 | ) 467 | ); 468 | }); 469 | 470 | $result = null; 471 | 472 | $eventStore->attach( 473 | ActionEventEmitterEventStore::EVENT_CREATE, 474 | function (ActionEvent $event) use (&$result): void { 475 | $stream = $event->getParam('stream'); 476 | $stream->streamEvents()->rewind(); 477 | $result = $stream->streamEvents()->current(); 478 | }, 479 | -1000 480 | ); 481 | 482 | $router->attachToMessageBus($commandBus); 483 | $causationMetadataEnricher->attachToMessageBus($commandBus); 484 | 485 | $command = new DoSomething(['name' => 'Alex'], 1); 486 | $commandBus->dispatch($command); 487 | 488 | $this->assertArrayHasKey($idKey, $result->metadata()); 489 | $this->assertArrayHasKey($nameKey, $result->metadata()); 490 | 491 | $this->assertEquals($command->uuid()->toString(), $result->metadata()[$idKey]); 492 | $this->assertEquals(\get_class($command), $result->metadata()[$nameKey]); 493 | } 494 | 495 | /** 496 | * @test 497 | */ 498 | public function causation_id_key_cannot_be_empty(): void 499 | { 500 | $this->expectException(InvalidArgumentException::class); 501 | 502 | new CausationMetadataEnricher(''); 503 | } 504 | 505 | /** 506 | * @test 507 | */ 508 | public function causation_name_key_cannot_be_empty(): void 509 | { 510 | $this->expectException(InvalidArgumentException::class); 511 | 512 | new CausationMetadataEnricher('$causationId', ''); 513 | } 514 | 515 | private function getEventStore(): ActionEventEmitterEventStore 516 | { 517 | return new ActionEventEmitterEventStore(new InMemoryEventStore(), new ProophActionEventEmitter()); 518 | } 519 | } 520 | -------------------------------------------------------------------------------- /tests/Container/EventPublisherFactoryTest.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2015-2021 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace ProophTest\EventStoreBusBridge\Container; 15 | 16 | use PHPUnit\Framework\TestCase; 17 | use Prooph\EventStoreBusBridge\Container\EventPublisherFactory; 18 | use Prooph\EventStoreBusBridge\EventPublisher; 19 | use Prooph\EventStoreBusBridge\Exception\InvalidArgumentException; 20 | use Prooph\ServiceBus\EventBus; 21 | use Prophecy\PhpUnit\ProphecyTrait; 22 | use Psr\Container\ContainerInterface; 23 | 24 | class EventPublisherFactoryTest extends TestCase 25 | { 26 | use ProphecyTrait; 27 | 28 | /** 29 | * @test 30 | */ 31 | public function it_creates_an_event_publisher_using_the_default_service_name_for_getting_an_event_bus(): void 32 | { 33 | $eventBus = $this->prophesize(EventBus::class); 34 | 35 | $container = $this->prophesize(ContainerInterface::class); 36 | 37 | $container->get(EventBus::class)->willReturn($eventBus->reveal()); 38 | 39 | $factory = new EventPublisherFactory(); 40 | 41 | $eventPublisher = $factory($container->reveal()); 42 | 43 | $this->assertInstanceOf(EventPublisher::class, $eventPublisher); 44 | } 45 | 46 | /** 47 | * @test 48 | */ 49 | public function it_creates_an_event_publisher_via_callstatic(): void 50 | { 51 | $eventBus = $this->prophesize(EventBus::class); 52 | 53 | $container = $this->prophesize(ContainerInterface::class); 54 | 55 | $container->get('foo')->willReturn($eventBus->reveal()); 56 | 57 | $type = 'foo'; 58 | $eventPublisher = EventPublisherFactory::$type($container->reveal()); 59 | 60 | $this->assertInstanceOf(EventPublisher::class, $eventPublisher); 61 | } 62 | 63 | /** 64 | * @test 65 | */ 66 | public function it_throws_exception_when_invalid_container_passed_to_callstatic(): void 67 | { 68 | $this->expectException(InvalidArgumentException::class); 69 | 70 | $type = 'foo'; 71 | EventPublisherFactory::$type('invalid container'); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tests/Container/TransactionManagerFactoryTest.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2015-2021 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace ProophTest\EventStoreBusBridge\Container; 15 | 16 | use PHPUnit\Framework\TestCase; 17 | use Prooph\EventStore\EventStore; 18 | use Prooph\EventStore\TransactionalEventStore; 19 | use Prooph\EventStoreBusBridge\Container\TransactionManagerFactory; 20 | use Prooph\EventStoreBusBridge\Exception\InvalidArgumentException; 21 | use Prooph\EventStoreBusBridge\TransactionManager; 22 | use Prophecy\PhpUnit\ProphecyTrait; 23 | use Psr\Container\ContainerInterface; 24 | 25 | class TransactionManagerFactoryTest extends TestCase 26 | { 27 | use ProphecyTrait; 28 | 29 | /** 30 | * @test 31 | */ 32 | public function it_creates_a_transaction_manager(): void 33 | { 34 | $eventStore = $this->prophesize(TransactionalEventStore::class); 35 | 36 | $container = $this->prophesize(ContainerInterface::class); 37 | 38 | $container->get(EventStore::class)->willReturn($eventStore->reveal()); 39 | 40 | $factory = new TransactionManagerFactory(); 41 | 42 | $transactionManager = $factory($container->reveal()); 43 | 44 | $this->assertInstanceOf(TransactionManager::class, $transactionManager); 45 | } 46 | 47 | /** 48 | * @test 49 | */ 50 | public function it_creates_a_transaction_manager_via_callstatic(): void 51 | { 52 | $eventStore = $this->prophesize(TransactionalEventStore::class); 53 | 54 | $container = $this->prophesize(ContainerInterface::class); 55 | 56 | $container->get('foo')->willReturn($eventStore->reveal()); 57 | 58 | $type = 'foo'; 59 | $transactionManager = TransactionManagerFactory::$type($container->reveal()); 60 | 61 | $this->assertInstanceOf(TransactionManager::class, $transactionManager); 62 | } 63 | 64 | /** 65 | * @test 66 | */ 67 | public function it_throws_exception_when_invalid_container_passed_to_callstatic(): void 68 | { 69 | $this->expectException(InvalidArgumentException::class); 70 | 71 | $type = 'foo'; 72 | TransactionManagerFactory::$type('invalid container'); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /tests/EventPublisherTest.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2015-2021 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace ProophTest\EventStoreBusBridge; 15 | 16 | use PHPUnit\Framework\TestCase; 17 | use Prooph\Common\Event\ProophActionEventEmitter; 18 | use Prooph\Common\Messaging\Message; 19 | use Prooph\EventStore\ActionEventEmitterEventStore; 20 | use Prooph\EventStore\EventStore; 21 | use Prooph\EventStore\Exception\ConcurrencyException; 22 | use Prooph\EventStore\Exception\StreamExistsAlready; 23 | use Prooph\EventStore\Exception\StreamNotFound; 24 | use Prooph\EventStore\InMemoryEventStore; 25 | use Prooph\EventStore\Stream; 26 | use Prooph\EventStore\StreamName; 27 | use Prooph\EventStore\TransactionalActionEventEmitterEventStore; 28 | use Prooph\EventStoreBusBridge\EventPublisher; 29 | use Prooph\ServiceBus\EventBus; 30 | use Prophecy\PhpUnit\ProphecyTrait; 31 | 32 | class EventPublisherTest extends TestCase 33 | { 34 | use ProphecyTrait; 35 | 36 | /** 37 | * @var ActionEventEmitterEventStore 38 | */ 39 | private $eventStore; 40 | 41 | protected function setUp(): void 42 | { 43 | $this->eventStore = new TransactionalActionEventEmitterEventStore(new InMemoryEventStore(), new ProophActionEventEmitter()); 44 | } 45 | 46 | /** 47 | * @test 48 | */ 49 | public function it_publishes_all_created_and_appended_events_if_not_inside_transaction(): void 50 | { 51 | [$event1, $event2, $event3, $event4] = $this->setupStubEvents(); 52 | 53 | $eventBus = $this->prophesize(EventBus::class); 54 | 55 | $eventPublisher = new EventPublisher($eventBus->reveal()); 56 | 57 | $eventPublisher->attachToEventStore($this->eventStore); 58 | 59 | $eventBus->dispatch($event1)->shouldBeCalled(); 60 | $eventBus->dispatch($event2)->shouldBeCalled(); 61 | $eventBus->dispatch($event3)->shouldBeCalled(); 62 | $eventBus->dispatch($event4)->shouldBeCalled(); 63 | 64 | $this->eventStore->create(new Stream(new StreamName('test'), new \ArrayIterator([$event1, $event2]))); 65 | $this->eventStore->appendTo(new StreamName('test'), new \ArrayIterator([$event3, $event4])); 66 | } 67 | 68 | /** 69 | * @test 70 | */ 71 | public function it_publishes_all_created_and_appended_events(): void 72 | { 73 | [$event1, $event2, $event3, $event4] = $this->setupStubEvents(); 74 | 75 | $eventBus = $this->prophesize(EventBus::class); 76 | 77 | $eventBus->dispatch($event1)->shouldBeCalled(); 78 | $eventBus->dispatch($event2)->shouldBeCalled(); 79 | $eventBus->dispatch($event3)->shouldBeCalled(); 80 | $eventBus->dispatch($event4)->shouldBeCalled(); 81 | 82 | $eventPublisher = new EventPublisher($eventBus->reveal()); 83 | 84 | $eventPublisher->attachToEventStore($this->eventStore); 85 | 86 | $this->eventStore->beginTransaction(); 87 | $this->eventStore->create(new Stream(new StreamName('test'), new \ArrayIterator([$event1, $event2]))); 88 | $this->eventStore->appendTo(new StreamName('test'), new \ArrayIterator([$event3, $event4])); 89 | $this->eventStore->commit(); 90 | } 91 | 92 | /** 93 | * @test 94 | */ 95 | public function it_publishes_correctly_when_event_store_implements_can_control_transaction(): void 96 | { 97 | [$event1, $event2, $event3, $event4] = $this->setupStubEvents(); 98 | 99 | $eventBus = $this->prophesize(EventBus::class); 100 | 101 | $eventBus->dispatch($event1)->shouldBeCalled(); 102 | $eventBus->dispatch($event2)->shouldBeCalled(); 103 | $eventBus->dispatch($event3)->shouldBeCalled(); 104 | $eventBus->dispatch($event4)->shouldBeCalled(); 105 | 106 | $eventPublisher = new EventPublisher($eventBus->reveal()); 107 | 108 | $eventPublisher->attachToEventStore($this->eventStore); 109 | 110 | $this->eventStore->beginTransaction(); 111 | $this->eventStore->create(new Stream(new StreamName('test'), new \ArrayIterator([$event1, $event2]))); 112 | $this->eventStore->appendTo(new StreamName('test'), new \ArrayIterator([$event3, $event4])); 113 | $this->eventStore->commit(); 114 | } 115 | 116 | /** 117 | * @test 118 | */ 119 | public function it_does_not_publish_when_event_store_rolls_back(): void 120 | { 121 | [$event1, $event2, $event3, $event4] = $this->setupStubEvents(); 122 | 123 | $eventBus = $this->prophesize(EventBus::class); 124 | 125 | $eventBus->dispatch($event1)->shouldNotBeCalled(); 126 | $eventBus->dispatch($event2)->shouldNotBeCalled(); 127 | $eventBus->dispatch($event3)->shouldNotBeCalled(); 128 | $eventBus->dispatch($event4)->shouldNotBeCalled(); 129 | 130 | $eventPublisher = new EventPublisher($eventBus->reveal()); 131 | 132 | $eventPublisher->attachToEventStore($this->eventStore); 133 | 134 | $this->eventStore->beginTransaction(); 135 | $this->eventStore->create(new Stream(new StreamName('test'), new \ArrayIterator([$event1, $event2]))); 136 | $this->eventStore->appendTo(new StreamName('test'), new \ArrayIterator([$event3, $event4])); 137 | $this->eventStore->rollback(); 138 | } 139 | 140 | /** 141 | * @test 142 | */ 143 | public function it_does_not_publish_when_non_transactional_event_store_throws_exception(): void 144 | { 145 | [$event1, $event2, $event3, $event4] = $this->setupStubEvents(); 146 | 147 | $eventStore = $this->prophesize(EventStore::class); 148 | $eventStore->create(new Stream(new StreamName('test'), new \ArrayIterator([$event1, $event2])))->willThrow(StreamExistsAlready::with(new StreamName('test')))->shouldBeCalled(); 149 | $eventStore->appendTo(new StreamName('test'), new \ArrayIterator([$event3, $event4]))->willThrow(new ConcurrencyException())->shouldBeCalled(); 150 | $eventStore->appendTo(new StreamName('unknown'), new \ArrayIterator([$event3, $event4]))->willThrow(StreamNotFound::with(new StreamName('unknown')))->shouldBeCalled(); 151 | 152 | $eventStore = new ActionEventEmitterEventStore($eventStore->reveal(), new ProophActionEventEmitter()); 153 | 154 | $eventBus = $this->prophesize(EventBus::class); 155 | 156 | $eventBus->dispatch($event1)->shouldNotBeCalled(); 157 | $eventBus->dispatch($event2)->shouldNotBeCalled(); 158 | $eventBus->dispatch($event3)->shouldNotBeCalled(); 159 | $eventBus->dispatch($event4)->shouldNotBeCalled(); 160 | 161 | $eventPublisher = new EventPublisher($eventBus->reveal()); 162 | 163 | $eventPublisher->attachToEventStore($eventStore); 164 | 165 | try { 166 | $eventStore->create(new Stream(new StreamName('test'), new \ArrayIterator([$event1, $event2]))); 167 | } catch (\Throwable $e) { 168 | // ignore 169 | } 170 | 171 | try { 172 | $eventStore->appendTo(new StreamName('test'), new \ArrayIterator([$event3, $event4])); 173 | } catch (\Throwable $e) { 174 | // ignore 175 | } 176 | 177 | try { 178 | $eventStore->appendTo(new StreamName('unknown'), new \ArrayIterator([$event3, $event4])); 179 | } catch (\Throwable $e) { 180 | // ignore 181 | } 182 | } 183 | 184 | /** 185 | * @return Message[] 186 | */ 187 | private function setupStubEvents(): array 188 | { 189 | $event1 = $this->prophesize(Message::class)->reveal(); 190 | $event2 = $this->prophesize(Message::class)->reveal(); 191 | $event3 = $this->prophesize(Message::class)->reveal(); 192 | $event4 = $this->prophesize(Message::class)->reveal(); 193 | 194 | return [$event1, $event2, $event3, $event4]; 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /tests/TransactionManagerTest.php: -------------------------------------------------------------------------------- 1 | 6 | * (c) 2015-2021 Sascha-Oliver Prolic 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace ProophTest\EventStoreBusBridge; 15 | 16 | use PHPUnit\Framework\TestCase; 17 | use Prooph\EventStore\TransactionalEventStore; 18 | use Prooph\EventStoreBusBridge\TransactionManager; 19 | use Prooph\ServiceBus\CommandBus; 20 | use Prooph\ServiceBus\Exception\MessageDispatchException; 21 | use Prooph\ServiceBus\Plugin\Router\CommandRouter; 22 | use Prophecy\PhpUnit\ProphecyTrait; 23 | 24 | class TransactionManagerTest extends TestCase 25 | { 26 | use ProphecyTrait; 27 | 28 | /** 29 | * @test 30 | */ 31 | public function it_handles_transactions(): void 32 | { 33 | $eventStore = $this->prophesize(TransactionalEventStore::class); 34 | 35 | $eventStore->beginTransaction()->shouldBeCalled(); 36 | $eventStore->inTransaction()->willReturn(true)->shouldBeCalled(); 37 | $eventStore->commit()->shouldBeCalled(); 38 | 39 | $transactionManager = new TransactionManager($eventStore->reveal()); 40 | 41 | $commandBus = new CommandBus(); 42 | $router = new CommandRouter(); 43 | $router->route('a message')->to(function () { 44 | }); 45 | 46 | $router->attachToMessageBus($commandBus); 47 | 48 | $transactionManager->attachToMessageBus($commandBus); 49 | 50 | $commandBus->dispatch('a message'); 51 | } 52 | 53 | /** 54 | * @test 55 | */ 56 | public function it_rolls_back_transactions(): void 57 | { 58 | $eventStore = $this->prophesize(TransactionalEventStore::class); 59 | 60 | $eventStore->beginTransaction()->shouldBeCalled(); 61 | $eventStore->inTransaction()->willReturn(true)->shouldBeCalled(); 62 | $eventStore->rollback()->shouldBeCalled(); 63 | 64 | $transactionManager = new TransactionManager($eventStore->reveal()); 65 | 66 | $commandBus = new CommandBus(); 67 | $router = new CommandRouter(); 68 | $router->route('a message')->to(function () { 69 | throw new \RuntimeException('foo'); 70 | }); 71 | 72 | $router->attachToMessageBus($commandBus); 73 | 74 | $transactionManager->attachToMessageBus($commandBus); 75 | 76 | try { 77 | $commandBus->dispatch('a message'); 78 | } catch (MessageDispatchException $e) { 79 | $this->assertInstanceOf(\RuntimeException::class, $e->getPrevious()); 80 | $this->assertEquals('foo', $e->getPrevious()->getMessage()); 81 | 82 | return; 83 | } 84 | 85 | $this->fail('No exception thrown'); 86 | } 87 | } 88 | --------------------------------------------------------------------------------