velpl / symfony-messenger-outbox-bundle
Outbox pattern using Symfony Messenger
Package info
github.com/velPL/symfony-messenger-outbox-bundle
Type:symfony-bundle
pkg:composer/velpl/symfony-messenger-outbox-bundle
Requires
- php: ^8.4 | ^8.5
- symfony/framework-bundle: ^7.4 | ^8.1
- symfony/messenger: ^7.4 | ^8.1
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.95
- phpstan/phpstan: ^2.2
- phpstan/phpstan-symfony: ^2.0
- phpunit/phpunit: ^13.2
- symfony/browser-kit: ^7.4 || ^8.1
- symfony/css-selector: ^7.4 || ^8.1
- symfony/dependency-injection: ^7.4 || ^8.1
- symfony/dotenv: ^7.4 || ^8.1
- symfony/flex: ^2.11
- symfony/http-kernel: ^7.4 || ^8.1
README
This Symfony bundle streamlines configuration of messenger transports and messages dispatching using the transactional outbox pattern. The benefit of using it is that you will be able to create a transaction which saves all your unit of work on entities into the database and dispatch a messenger event or command. Either both succeed or none – you always end up in a consistent state.
Requirements
- PHP: 8.4 or 8.5
- Symfony: 7.4 LTS or 8.1
Installation
⚠️ Please mind this is a work in progress and not yet a stable release – breaking changes are possible. Please provide feedback, suggestions and bug reports using GitHub issues. Pull requests are very welcome!
Install the bundle using Composer:
composer require velpl/symfony-messenger-outbox-bundle:^0.1
Configuration and usage
- Configure messenger buses that you want to use with the outbox pattern with the middleware provided by this bundle:
framework: messenger: buses: command.bus: middleware: # - ............... - 'Velpl\SymfonyMessengerOutboxBundle\Messenger\Outbox\Middleware\OutboxPatternMiddleware' # - ...............
- Configure the outbox pattern in the bundle configuration (be sure to pass the same connection name as your entity manager uses so that transaction actually can be performed):
# ............ transports: outbox: 'doctrine://default?queue_name=outbox'
- Create a message you want to dispatch and add an attribute pointing to the actual transport name that should handle it:
<?php declare(strict_types=1); namespace App\Message\Command; use Velpl\SymfonyMessengerOutboxBundle\Messenger\Outbox\Attribute\AsOutboxTransport; // ......... #[AsOutboxTransport('async_todo_creation')] final readonly class CreateTodoCommand { public function __construct(private Todo $todo) {} // rest of the class logic, usually some getters... }
You don't need to add messages with AsOutboxTransport to the messenger.yaml routing section
- Use entity manager to wrap your unit of work and dispatch the message in transaction:
<?php declare(strict_types=1); namespace App\Command; use App\Message\Command\CreateTodoCommand; use Doctrine\ORM\EntityManagerInterface; // .......... final readonly class TodoApplication service { public function __construct( private EntityManagerInterface $entityManager, private MessageBusInterface $messageBus ) {} public function create(): void { $todo = new Todo() ->setTitle('A todo list item') ; $this->entityManager->wrapInTransaction( function () use ($todo): void { $this->entityManager->persist($todo); $this->entityManager->flush(); $this->messageBus->dispatch(new CreateTodoCommand($todo)) } ); } }
-
Create actual handler for the
CreateTodoCommandmessage and wire it to thecommand.buswithAsMessageHandler(bus: 'command.bus')attriute -
Consume the outbox transport – it will read
AsOutboxTransporton outbox message and re-dispatch it to the appropriate transport (async_todo_creationin this example - you can then consume it from there using Doctrine or any other transport like RabbitMQ):
bin/console messenger:consume outbox
License
MIT