Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Symfony Messenger (Symfony Live San Francisco)

197 views

Published on

Symfony Messenger: an introduction to the component.

Published in: Software
  • Be the first to comment

  • Be the first to like this

Symfony Messenger (Symfony Live San Francisco)

  1. 1. Symfony Messenger Bus, Queues, workers and more !
  2. 2. Samuel Rozé VP Engineering @ Birdie Symfony Con(nuousPipe Tolerance
  3. 3. What's Symfony Messenger about? 1. Messages. Any serialisable PHP object. 2. Message bus. Where you dispatch your messages. 3. Message handlers. Will execute your business logic when the message arrives to them. 4. Transports. Allow to send and receive messages through 3rd party systems. 5. Worker. To consume messages from transports.
  4. 4. $ composer req symfony/messenger or composer req messenger
  5. 5. A message No specific requirement. namespace AppMessage; class SendNotification { // ...properties public function __construct(string $message, array $users) { $this->message = $message; $this->users = $users; } // ...getters }
  6. 6. As a component use SymfonyComponentMessengerMessageBus; use SymfonyComponentMessengerHandlerLocator; use SymfonyComponentMessengerMiddlewareHandleMessageMiddleware; $handler = function(SendNotification $message) { // ... }; $messageBus = new MessageBus([ new HandleMessageMiddleware(new HandlerLocator([ SendNotification::class => $handler, ])), ]);
  7. 7. In your Symfony applica0on Nothing to do...
  8. 8. Dispatching a message namespace AppController; use AppMessageSendNotification; // ... use SymfonyComponentMessengerMessageBusInterface; class DefaultController { public function index(MessageBusInterface $bus, Request $request) { $users = ['samuel', 'christelle']; $bus->dispatch(new SendNotification( $request->query->get('message', 'Hello London.'), $users )); return new Response('<html><body>OK.</body></html>'); } }
  9. 9. Dispatching a message namespace AppController; use AppMessageSendNotification; // ... use SymfonyComponentMessengerMessageBusInterface; class DefaultController { public function index(MessageBusInterface $bus, Request $request) { $users = ['samuel', 'christelle']; $bus->dispatch(new SendNotification( $request->query->get('message', 'Hello London.'), $users )); return new Response('<html><body>OK.</body></html>'); } }
  10. 10. A message handler namespace AppMessageHandler; use AppMessageSendNotification; class SendNotificationHandler { public function __invoke(SendNotification $message) { foreach ($message->getUsers() as $user) { echo "Send notification to... ".$user."n"; } } }
  11. 11. Register your handler # config/services.yaml services: AppMessageHandlerSendNotificationHandler: tags: ['messenger.message_handler']
  12. 12. Register your handlers (op3on 2) # config/services.yaml services: AppMessageHandler: resource: '../src/MessageHandler/*' tags: ['messenger.message_handler']
  13. 13. Register your handlers (op3on 3) namespace AppMessageHandler; use AppMessageSendNotification; use SymfonyComponentMessengerHandlerMessageHandlerInterface; class SendNotificationHandler implements MessageHandlerInterface { public function __invoke(SendNotification $message) { foreach ($message->getUsers() as $user) { echo "Send notification to... ".$user."n"; } } }
  14. 14. Yay, dispatched ! Handler is called. Done.
  15. 15. Now, imagine your handler takes ages to run.
  16. 16. What's a transport? 1. Has a sender & receiver Guess what. They send and receive messages. 2. Is configurable via a DSN So we have a unique and common way of configuring them. 3. Use a Messenger Serializer By defaut, uses Symfony Serializer. Replace as you wish.
  17. 17. Transports 1. Built-in AMQP. You don't need anything more than the component and the PHP extension. 2. More with Enqueue. (10+ other transports) h>ps://github.com/php-enqueue/messenger-adapter 3. Fully extensible. Create your own very easily by registering your implementaIon of the TransportFactory interface.
  18. 18. Configure your transport(s) # config/packages/messenger.yaml framework: messenger: transports: default: '%env(MESSENGER_DSN)%' # .env MESSENGER_DSN=amqp://guest:guest@localhost:5672/%2f/messages
  19. 19. Route your message(s) framework: messenger: routing: 'AppMessageSendNotification': default
  20. 20. $ bin/console messenger:consume-messages
  21. 21. Mul$ple buses? framework: messenger: default_bus: command_bus buses: command_bus: ~ event_bus: ~
  22. 22. Auto-wiring trick # config/services.yaml services: _defaults: autowire: true autoconfigure: true public: false bind: $eventBus: '@event_bus'
  23. 23. Auto-wiring trick use SymfonyComponentMessengerMessageBusInterface; class MyController { public function __construct( MessageBusInterface $commandBus, MessageBusInterface $eventBus ) { // ... } // ... }
  24. 24. Register your handlers (op3on 4) namespace AppMessageHandler; use AppMessageSendNotification; use SymfonyComponentMessengerHandlerMessageSubscriberInterface; class SendNotificationHandler implements MessageSubscriberInterface { public static function getHandledMessages(): iterable { yield SendNotification::class => [ 'method' => 'doSomething', 'bus' => 'command_bus' ]; } public function doSomething(SendNotification $message) { // ... } }
  25. 25. Register your handlers (yield !!) namespace AppMessageHandler; use SymfonyComponentMessengerHandlerMessageSubscriberInterface; class SendNotificationHandler implements MessageSubscriberInterface { public static function getHandledMessages(): iterable { yield MyMessage::class => ['bus' => 'first_bus']; yield MyMessage::class => ['bus' => 'second_bus']; } public function __invoke(MyMessage $message) { // ... } }
  26. 26. Middleware(s)
  27. 27. Middleware namespace AppMiddleware; use SymfonyComponentMessengerMiddlewareMiddlewareInterface; class AuditMiddleware implements MiddlewareInterface { public function handle($message, callable $next) { try { echo sprintf('Started with message "%s"'."n", get_class($message)); return $next($message); } finally { echo sprintf('Ended with message "%s"'."n", get_class($message)); } } }
  28. 28. Add your middleware framework: messenger: buses: command_bus: middleware: - 'AppMiddlewareAuditMiddleware' event_bus: ~
  29. 29. Envelopes For non-domain logic...
  30. 30. Envelopes namespace AppMiddleware; use SymfonyComponentMessengerMiddlewareMiddlewareInterface; use SymfonyComponentMessengerEnvelopeAwareInterface; use SymfonyComponentMessengerAsynchronousTransportReceivedMessage; class AuditMiddleware implements MiddlewareInterface, EnvelopeAwareInterface { public function handle($envelope, callable $next) { try { if (null !== $envelope->get(ReceivedMessage::class)) { echo sprintf('Consumed message "%s"'."n", get_class($message)); } else { echo sprintf('Dispatched message "%s"'."n", get_class($message)); } return $next($envelope); } finally { echo sprintf('Ended with message "%s"'."n", get_class($message)); } } }
  31. 31. Envelopes namespace AppMiddleware; use SymfonyComponentMessengerMiddlewareMiddlewareInterface; use SymfonyComponentMessengerEnvelopeAwareInterface; use SymfonyComponentMessengerAsynchronousTransportReceivedMessage; class AuditMiddleware implements MiddlewareInterface, EnvelopeAwareInterface { public function handle($envelope, callable $next) { try { if (null !== $envelope->get(ReceivedMessage::class)) { echo sprintf('Consumed message "%s"'."n", get_class($message)); } else { echo sprintf('Dispatched message "%s"'."n", get_class($message)); } return $next($envelope); } finally { echo sprintf('Ended with message "%s"'."n", get_class($message)); } } }
  32. 32. Your own Envelope item use SymfonyComponentMessengerEnvelopeAwareInterface; use SymfonyComponentMessengerMiddlewareMiddlewareInterface; class AuditMiddleware implements MiddlewareInterface, EnvelopeAwareInterface { public function handle($envelope, callable $next) { $message = $envelope->getMessage(); if (null === $auditEnvelope = $envelope->get(AuditEnvelopeItem::class)) { $envelope = $envelope->with( $auditEnvelope = new AuditEnvelopeItem(uniqid()) ); } try { echo sprintf('[%s] Started with message "%s"' . "n", $auditEnvelope->getUuid(), get_class($message)); return $next($envelope); } finally { echo sprintf('[%s] Ended with message "%s"'."n", $auditEnvelope->getUuid(), get_class($message)); } } }
  33. 33. Your own Envelope item use SymfonyComponentMessengerEnvelopeItemInterface; class AuditEnvelopeItem implements EnvelopeItemInterface { private $uuid; public function __construct(string $uuid) { $this->uuid = $uuid; } public function getUuid() { return $this->uuid; } }
  34. 34. Envelopes: also for "configura3on" use SymfonyComponentMessengerEnvelope; use SymfonyComponentMessengerTransport SerializationSerializerConfiguration; $bus->dispatch( (new Envelope($message))->with(new SerializerConfiguration([ 'groups' => ['my_serialization_groups'], ])) );
  35. 35. What can you do? 1. Try it! It has arrived in Symfony 4.1. Stable in 4.2. 2. Help us make it great again. Open an issue, a pull-request, create another transport, a set of middleware, ... 3. Enjoy.
  36. 36. Thank you. h"ps://symfony.com/messenger @samuelroze

×