Wrapping a handler in a transaction inside the handler means every handler repeats it, and the one that forgets is the one that leaves half a write.
framework:
messenger:
buses:
command.bus:
middleware:
- validation
- doctrine_transaction
- AppMessengerCorrelationIdMiddleware
# the handler is now free of both concerns and testable without either
Ordering matters and is not obvious: validation before the transaction means an invalid message never opens one, and the correlation middleware belongs outside both so that a rollback is still logged with its id. A custom middleware is one method receiving the envelope and a callable for the rest of the stack, which is the same shape as HTTP middleware and immediately legible. Putting a transaction on an event bus rather than a command bus is usually a mistake, since events are supposed to be independent.