From 804972fb735af5f20a4bc358ab3778d3e7b601ab Mon Sep 17 00:00:00 2001 From: Lctrs Date: Wed, 17 Jul 2019 01:34:36 +0200 Subject: [PATCH 1/3] Add EventStoreTransactionMiddleware to be used with symfony/messenger --- composer.json | 3 +- .../EventStoreTransactionMiddleware.php | 50 +++++++++++++++++++ .../EventStoreTransactionMiddlewareTest.php | 49 ++++++++++++++++++ 3 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 src/Messenger/EventStoreTransactionMiddleware.php create mode 100644 test/Messenger/EventStoreTransactionMiddlewareTest.php diff --git a/composer.json b/composer.json index b3de0e7..551772e 100644 --- a/composer.json +++ b/composer.json @@ -69,7 +69,8 @@ "friendsofphp/php-cs-fixer": "^2.8.1", "prooph/php-cs-fixer-config": "^0.2.1", "matthiasnoback/symfony-dependency-injection-test": "^2.3", - "phpstan/phpstan": "^0.9.2" + "phpstan/phpstan": "^0.9.2", + "symfony/messenger": "^4.3" }, "suggest": { "prooph/event-store-bus-bridge": "To Marry CQRS (ProophSerivceBus) with Event Sourcing" diff --git a/src/Messenger/EventStoreTransactionMiddleware.php b/src/Messenger/EventStoreTransactionMiddleware.php new file mode 100644 index 0000000..1686edb --- /dev/null +++ b/src/Messenger/EventStoreTransactionMiddleware.php @@ -0,0 +1,50 @@ +eventStore = $eventStore; + } + + public function handle(Envelope $envelope, StackInterface $stack): Envelope + { + $this->eventStore->beginTransaction(); + + try { + $envelope = $stack->next()->handle($envelope, $stack); + + $this->eventStore->commit(); + } catch (Throwable $e) { + $this->eventStore->rollback(); + + if ($e instanceof HandlerFailedException) { + // Remove all HandledStamp from the envelope so the retry will execute all handlers again. + // When a handler fails, the queries of allegedly successful previous handlers just got rolled back. + throw new HandlerFailedException( + $e->getEnvelope()->withoutAll(HandledStamp::class), + $e->getNestedExceptions() + ); + } + + throw $e; + } + + return $envelope; + } +} diff --git a/test/Messenger/EventStoreTransactionMiddlewareTest.php b/test/Messenger/EventStoreTransactionMiddlewareTest.php new file mode 100644 index 0000000..eb46610 --- /dev/null +++ b/test/Messenger/EventStoreTransactionMiddlewareTest.php @@ -0,0 +1,49 @@ +eventStore = $this->createMock(TransactionalEventStore::class); + $this->middleware = new EventStoreTransactionMiddleware($this->eventStore); + } + + public function testMiddlewareWrapsInTransactionAndFlushes(): void + { + $this->eventStore->expects($this->once()) + ->method('beginTransaction'); + $this->eventStore->expects($this->once()) + ->method('commit'); + + $this->middleware->handle(new Envelope(new stdClass()), $this->getStackMock()); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Thrown from next middleware. + */ + public function testTransactionIsRolledBackOnException(): void + { + $this->eventStore->expects($this->once()) + ->method('beginTransaction'); + $this->eventStore->expects($this->once()) + ->method('rollback'); + + $this->middleware->handle(new Envelope(new stdClass()), $this->getThrowingStackMock()); + } +} From 60ea12b6dbe71bfd33827589a54085f7dff663b3 Mon Sep 17 00:00:00 2001 From: Lctrs Date: Wed, 17 Jul 2019 12:54:15 +0200 Subject: [PATCH 2/3] Add a test for resetting handled stamps on handler failed exception --- .../EventStoreTransactionMiddlewareTest.php | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/test/Messenger/EventStoreTransactionMiddlewareTest.php b/test/Messenger/EventStoreTransactionMiddlewareTest.php index eb46610..fad070f 100644 --- a/test/Messenger/EventStoreTransactionMiddlewareTest.php +++ b/test/Messenger/EventStoreTransactionMiddlewareTest.php @@ -4,11 +4,15 @@ namespace ProophTest\Bundle\EventStore\Messenger; +use LogicException; use Prooph\Bundle\EventStore\Messenger\EventStoreTransactionMiddleware; use Prooph\EventStore\TransactionalEventStore; use stdClass; use Symfony\Component\Messenger\Envelope; +use Symfony\Component\Messenger\Exception\HandlerFailedException; +use Symfony\Component\Messenger\Stamp\HandledStamp; use Symfony\Component\Messenger\Test\Middleware\MiddlewareTestCase; +use Throwable; class EventStoreTransactionMiddlewareTest extends MiddlewareTestCase { @@ -46,4 +50,29 @@ public function testTransactionIsRolledBackOnException(): void $this->middleware->handle(new Envelope(new stdClass()), $this->getThrowingStackMock()); } + + public function testItResetsHandledStampsOnHandlerFailedException(): void + { + $this->eventStore->expects($this->once()) + ->method('beginTransaction'); + $this->eventStore->expects($this->once()) + ->method('rollback'); + + $envelop = new Envelope(new stdClass(), [ + new HandledStamp('dummy', 'dummy'), + ]); + + $exception = null; + try { + $this->middleware->handle($envelop, $this->getThrowingStackMock(new HandlerFailedException($envelop, [ + new LogicException('dummy exception'), + ]))); + } catch (Throwable $e) { + $exception = $e; + } + + $this->assertInstanceOf(HandlerFailedException::class, $exception); + /** @var HandlerFailedException $exception */ + $this->assertSame([], $exception->getEnvelope()->all(HandledStamp::class)); + } } From fc4f5fab9708847fbe8f7a78ab9227b32d73a94e Mon Sep 17 00:00:00 2001 From: Lctrs Date: Sun, 6 Oct 2019 04:26:30 +0200 Subject: [PATCH 3/3] add doc --- doc/bookdown.json | 3 ++- doc/messenger.md | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 doc/messenger.md diff --git a/doc/bookdown.json b/doc/bookdown.json index 1a2f5e9..c949fb0 100644 --- a/doc/bookdown.json +++ b/doc/bookdown.json @@ -6,7 +6,8 @@ {"event_store": "event_store.md"}, {"projection_manager": "projection_manager.md"}, {"event_store_bus_bridge": "event_store_bus_bridge.md"}, - {"configuration_reference": "configuration_reference.md"} + {"configuration_reference": "configuration_reference.md"}, + {"messenger": "messenger.md"} ], "target": "./html", "tocDepth": 2, diff --git a/doc/messenger.md b/doc/messenger.md new file mode 100644 index 0000000..4e9db12 --- /dev/null +++ b/doc/messenger.md @@ -0,0 +1,24 @@ +# Messenger integration + +This bundle provides a middleware for the +`symfony/messenger` component (from version `4.3`) which handles +starting/committing/rolling back a transaction when sending a command +to the bus. + +Here is an example configuration on how to use it: +```yaml +# app/config/messenger.yaml + +framework: + messenger: + buses: + command.bus: + middleware: + - my_eventstore_transaction_middleware + +services: + my_eventstore_transaction_middleware: + class: Prooph\Bundle\EventStore\Messenger\EventStoreTransactionMiddleware + arguments: + - '@my_transactional_event_store' +```