Eine Einführung in die Messenger-Komponente

Eine Einführung in die Messenger-Komponente

Vortrag auf der Symfony User Group Hamburg.

Themen sind die Konzepte rund um die Messenger-Komponente (Message Bus, Message, Handler, Middleware, Envelopes und Stamps), ein Vergleich mit dem EventDispatcher sowie Beispiele für Konfiguration & Verwendung anhand einer Demoanwendung.

Der Code für die gezeigte Demo befindet sich auf GitHub: https://github.com/dbrumann/messenger-screenshot-demo

6a1345d8e6dd15b2c78eff0c331963b1?s=128

Denis Brumann

March 05, 2019
Tweet

Transcript

  1. Symfony Messenger Eine Einführung in die Messenger-Komponente

  2. Denis Brumann denis.brumann@sensiolabs.de @dbrumann

  3. Was ist die
 Messenger-Komponente?

  4. …helps applications send and receive messages to/ from other applications

    [...]. It provides a message bus and some routing capabilities to send messages in any service where you need it https://symfony.com/blog/new-in-symfony-4-1-messenger-component
  5. MessageBus

  6. $messageBus->dispatch( new TakeScreenshotCommand($url) );

  7. sendet Nachrichten delegiert Nachrichten an Handler oder
 versendet/empfängt sie über

    einen Transport
  8. $eventDispatcher->dispatch( 'app.take_screenshot', new TakeScreenshotEvent($url) );

  9. Message

  10. <?php declare(strict_types = 1); namespace App\Domain\Screenshot; class TakeScreenshotCommand { private

    $url; public function __construct(string $url) { $this->url = $url; } public function getUrl(): string { return $this->url; } }
  11. beschreibt Daten serialisierbare, frameworkunabhängige Datenklasse

  12. <?php declare(strict_types = 1); namespace App\Domain\Screenshot; use Symfony\Component\EventDispatcher\Event; class TakeScreenshotEvent

    extends Event { private $url; public function __construct(string $url) { $this->url = $url; } public function getUrl(): string { return $this->url; } }
  13. MessageHandler

  14. None
  15. Handler erforderlich bei Standardkonfiguration und synchroner Verarbeitung, muss ein Handler

    registriert sein
  16. <?php declare(strict_types = 1); namespace App\Domain\Screenshot; use Symfony\Component\Messenger\Handler\MessageHandlerInterface; use Symfony\Component\Messenger\MessageBusInterface;

    class TakeScreenshotHandler implements MessageHandlerInterface { ... public function __invoke(TakeScreenshotCommand $takeScreenshot) { $screenshot = $this->client->takeScreenshot($takeScreenshot->getUrl()); $this->messageBus->dispatch(new ScreenshotTakenEvent($screenshot)); } }
  17. verarbeitet Daten verarbeitet Messages, die über den Bus delegiert oder

    über einen Transport empfangen wurden
  18. namespace App\Domain\Screenshot; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; class TakeScreenshotEventSubscriber implements EventSubscriberInterface

    { ... public static function getSubscribedEvents() { return ['app.take_screenshot' => ['takeScreenshot']]; } public function takeScreenshot(TakeScreenshotEvent $event) { $screenshot = $this->client->takeScreenshot($event->getUrl()); $this->eventDispatcher->dispatch(
 'app.save_screenshot', new SaveScreenshotEvent($screenshot) ); } }
  19. MessageHandlerInterface

  20. <?php /* * This file is part of the Symfony

    package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Messenger\Handler; /** * Marker interface for message handlers. * * @author Samuel Roze <samuel.roze@gmail.com> * * @experimental in 4.2 */ interface MessageHandlerInterface { }
  21. Zero Config Marker Interface registriert Handler im MessageBus
 mittels autowire

    + autoconfigure
  22. # config/services.yaml services: App\Domain\Screenshot\TakeScreenshotHandler: tags: - { name: 'messenger.message_handler' }

  23. konfigurierbar Attribute erlauben freie Konfiguration des Handlers

  24. App\Domain\Screenshot\TakeScreenshotHandler: tags: - name: 'messenger.message_handler' bus: 'messenger.bus.default' method: 'takeScreenshot' handles:

    'App\Domain\Screenshot\TakeScreenshotCommand' priority: 0
  25. MessageSubscriber Alternative zu HandlerInterface und Konfiguration

  26. public static function getHandledMessages(): iterable { yield [TakeScreenshotCommand::class => ['takeDesktopScreenshot',

    0]]; yield [TakeScreenshotCommand::class => ['takeMobileScreenshot', 0]]; } public function takeDesktopScreenshot(
 TakeScreenshotCommand $takeScreenshot ) { ... } public function takeMobileScreenshot( TakeScreenshotCommand $takeScreenshot ) { ... }
  27. Demo Time http://localhost:8000

  28. Wie werden
 Nachrichten geroutet?

  29. public static function getHandledMessages(): iterable { yield [TakeScreenshotCommand::class => ['takeScreenshot',

    0]]; }
  30. Message + Methode Handler/Subscriber geben an, welche Messages
 sie verarbeiten

    können
  31. public function __invoke(TakeScreenshotCommand $takeScreenshot)

  32. Invokable Wenn keine Methode angegeben wurde, wird als Fallback auf

    __invoke + Argument geprüft
  33. Wie werden
 Nachrichten verarbeitet?

  34. # config/packages/messenger.yaml framework: messenger: buses: default: default_middleware: true # alternatives:

    false; "allow_no_handlers" middleware: - 'messenger.middleware.validation'
  35. Middleware Verhalten des Message Bus wird durch Middleware
 gesteuert

  36. $defaultMiddleware = [ 'before' => [['id' => 'logging']], 'after' =>

    [ ['id' => 'send_message'], ['id' => 'handle_message'] ], ];
  37. LoggingMiddleware enthält Debug-Informationen und ist über den
 WebProfiler erreichbar

  38. None
  39. None
  40. Defaults Logging vor den eigenen Middlewares, nach den eigenen Middlewares:

    Transports bzw. Handler
  41. public function handle(Envelope $envelope, StackInterface $stack): Envelope { ... if

    (null === $sender || $handle) { return $stack->next()->handle($envelope, $stack); } // message should only be sent and not be handled by the next middleware return $envelope; }
  42. SendMessage Wenn eine Nachricht versandt wurde, wird die
 HandleMessageMiddleware nicht

    getriggert
  43. if ($envelope->all(ReceivedStamp::class)) { // it's a received message, do not

    send it back return $stack->next()->handle($envelope, $stack); }
  44. Circular References SendMessageMiddleware prüft, ob Nachricht empfangen wurde und versendet

    sie nicht nochmal
  45. Was sind
 Envelopes und Stamps?

  46. $envelope = $message instanceof Envelope
 ? $message : new Envelope($message);

  47. Envelope Wrapper für Messages, der mit Stamps versehen werden kann

  48. final class ValidationStamp implements StampInterface { private $groups; /** *

    @param string[]|GroupSequence $groups */ public function __construct($groups) { $this->groups = $groups; } public function getGroups() { return $this->groups; } }
  49. Stamps Beschreibt Metadaten, die als Kontext für Middleware verwendet werden

  50. public function handle(Envelope $envelope, StackInterface $stack): Envelope { $message =

    $envelope->getMessage(); $groups = null; /** @var ValidationStamp|null $validationStamp */ if ($validationStamp = $envelope->last(ValidationStamp::class)) { $groups = $validationStamp->getGroups(); } $violations = $this->validator->validate($message, null, $groups); if (\count($violations)) { throw new ValidationFailedException($message, $violations); } return $stack->next()->handle($envelope, $stack); }
  51. /** @var ValidationStamp|null $validationStamp */ if ($validationStamp = $envelope->last(ValidationStamp::class)) {

    $groups = $validationStamp->getGroups(); } $violations = $this->validator->validate($message, null, $groups);
  52. None
  53. Wie versendet
 man Nachrichten?

  54. pecl install amqp

  55. AMQP-Transport Aktuell wird nur die native AMQP-Bibliothek unterstützt

  56. composer require serializer

  57. Symfony Serializer Nachrichten werden JSON-serialisiert an den Message Broker übermittelt

  58. # config/packages/messenger.yaml framework: messenger: serializer: id: messenger.transport.symfony_serializer format: json

  59. Alternative Serializer Anbindung anderer Bibliotheken über Adapter via eigenes SerializerInterface

  60. # config/packages/messenger.yaml framework: messenger: transports: amqp: '%env(MESSENGER_TRANSPORT_DSN)%' routing: '*': senders:

    [amqp] send_and_handle: false
  61. Routing Nachrichten definieren, über welchen Transport sie versandt werden, unabhängig

    vom Message Bus
  62. Another Demo http://localhost:8000

  63. None
  64. None
  65. None
  66. None