SF
{ }
Symfony · Messenger · Event-Driven · CQRS
Symfony Messenger: Event-Driven
Architecture von Grund auf

Synchrone Systeme skalieren bis zu einem Punkt — dann werden HTTP-Requests zu lang, Datenbankverbindungen stapeln sich, und ein fehlerhafter Dienst blockiert das gesamte System. Symfony Messenger löst genau diese Probleme durch eine klare Trennung von Kommando, Ereignis und Abfrage — verbunden mit asynchronen Transports, die Nachrichten zuverlässig auch bei Systemausfällen zustellen.

20 Min. Lesezeit Messages · Handler · Transports · Middlewares · Domain Events Symfony 7.x · PHP 8.3+ · RabbitMQ · Redis

1. Warum Event-Driven Architecture mit Symfony Messenger?

Eine Event-Driven Architecture mit Symfony Messenger löst ein fundamentales Skalierungsproblem: Wenn ein Benutzer eine Bestellung aufgibt, muss das System gleichzeitig die Bestellung persistieren, eine Bestätigungsmail senden, den Lagerbestand aktualisieren und ein CRM-Event auslösen. Synchron in einem HTTP-Request erledigt, verlängert jeder dieser Schritte die Antwortzeit. Schlägt einer fehl, scheitert die gesamte Transaktion. Symfony Messenger entkoppelt diese Schritte: Die Bestellung wird gespeichert, eine Message wird in eine Queue gestellt, und alle weiteren Schritte laufen asynchron in separaten Worker-Prozessen.

Der zweite Vorteil der Event-Driven Architecture ist die strukturelle Entkopplung von Systemkomponenten. Ein Rechnungsmodul muss nichts über das Benachrichtigungsmodul wissen — es dispatcht ein OrderPlacedEvent, und alle interessierten Handler reagieren darauf unabhängig voneinander. Neue Anforderungen wie ein Treuepunkte-System lassen sich hinzufügen, indem man einen weiteren Handler registriert, ohne bestehenden Code anzufassen. Symfony Messenger implementiert dieses Muster mit einem Message Bus, der Messages an registrierte Handler weiterleitet und dabei Transports, Middlewares und Retry-Strategien transparent verwaltet.

2. Messages, Commands, Queries und Events – die Grundkonzepte

In einer Event-Driven Architecture mit Symfony Messenger gibt es drei fundamental verschiedene Nachrichtentypen, die sich in ihrer Semantik unterscheiden. Ein Command ist eine Absicht: PlaceOrderCommand enthält alle Daten, die nötig sind, um eine Bestellung aufzugeben. Commands haben genau einen Handler und ändern Zustand. Eine Query liest Daten ohne Seiteneffekte: GetOrderByIdQuery liefert eine Bestellung, ohne etwas zu verändern. Queries werden synchron verarbeitet, weil der Aufrufer das Ergebnis sofort braucht. Ein Event beschreibt, was passiert ist: OrderPlacedEvent teilt dem System mit, dass eine Bestellung eingegangen ist — ohne zu wissen oder zu bestimmen, was als nächstes geschieht.

Diese Trennung ist der Kern von CQRS (Command Query Responsibility Segregation) und lässt sich mit Symfony Messenger direkt umsetzen. Der MessageBusInterface ist der zentrale Einstiegspunkt: $this->commandBus->dispatch(new PlaceOrderCommand(...)). Symfony erlaubt mehrere Bus-Instanzen, sodass man einen Command-Bus, einen Query-Bus und einen Event-Bus separat konfigurieren kann — jeder mit eigenen Middlewares und Routing-Regeln. Ein Command-Bus erzwingt, dass jede Message genau einen Handler hat. Ein Event-Bus erlaubt null oder mehr Handler. Der Query-Bus gibt den Rückgabewert des Handlers zurück. Diese drei Busse abzugrenzen ist kein Overengineering, sondern schafft Klarheit über Intentionen im Code.


<?php

declare(strict_types=1);

namespace App\Order\Application\Command;

// Command — immutable value object carrying intent
final readonly class PlaceOrderCommand
{
    /**
     * @param list<array{productId: int, quantity: int}> $items
     */
    public function __construct(
        public readonly int $customerId,
        public readonly array $items,
        public readonly string $shippingAddress,
    ) {}
}

// Command Handler — single responsibility: place the order and dispatch events
namespace App\Order\Application\Command;

use App\Order\Domain\Event\OrderPlacedEvent;
use App\Order\Domain\Repository\OrderRepositoryInterface;
use App\Order\Domain\Service\OrderFactory;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
use Symfony\Component\Messenger\MessageBusInterface;

#[AsMessageHandler]
final class PlaceOrderCommandHandler
{
    public function __construct(
        private readonly OrderRepositoryInterface $orderRepository,
        private readonly OrderFactory $orderFactory,
        private readonly MessageBusInterface $eventBus,
    ) {}

    /**
     * Handle the PlaceOrderCommand: create, persist and publish order placed event.
     */
    public function __invoke(PlaceOrderCommand $command): void
    {
        $order = $this->orderFactory->createFromCommand($command);
        $this->orderRepository->save($order, flush: true);

        // Dispatch domain event — all interested handlers react independently
        $this->eventBus->dispatch(new OrderPlacedEvent(
            orderId: $order->getId(),
            customerId: $command->customerId,
            totalAmount: $order->getTotalAmount(),
            placedAt: new \DateTimeImmutable(),
        ));
    }
}

3. Handler: Nachrichten verarbeiten und Logik kapseln

Ein Symfony Messenger-Handler ist eine PHP-Klasse mit einer __invoke-Methode, die einen spezifischen Message-Typ entgegennimmt. Das Attribut #[AsMessageHandler] registriert die Klasse automatisch als Service und verbindet sie über den Typ-Hint mit dem passenden Message-Typ. Kein YAML, keine Interface-Implementierung — der Typ des Parameters bestimmt, für welche Message der Handler zuständig ist. Mehrere Handler für denselben Message-Typ sind möglich, wenn der Bus als Event-Bus konfiguriert ist. Bei einem Command-Bus würde eine zweite Handler-Registrierung für denselben Typ zu einem Fehler führen.

Handler sollten schlank bleiben: Sie koordinieren Domain-Services, rufen Repositories auf und dispatchen weitere Messages — aber sie enthalten keine Geschäftslogik selbst. Die Logik gehört in die Domain-Klassen. Ein SendOrderConfirmationHandler ruft einen MailerService auf, der das eigentliche Template rendert und die Mail versendet. Diese Trennung macht Handler testbar: Der Handler wird mit einem Mock des MailerService getestet, der Service selbst mit einem separaten Integration-Test. Symfony Messenger injiziert alle Abhängigkeiten des Handlers über den DI-Container automatisch — Constructor Property Promotion macht die Abhängigkeiten explizit und kompakt.

4. Transports: asynchrone Zustellung mit RabbitMQ und Redis

Ein Transport in Symfony Messenger ist die Verbindung zu einem externen Message-Broker oder einer Queue-Implementierung. AMQP für RabbitMQ, Redis Streams, Doctrine als Datenbank-Queue und Amazon SQS sind als Transports verfügbar. Der Standard-Transport ohne Konfiguration ist synchron — die Message wird sofort im selben Request verarbeitet. Das ist nützlich für Entwicklung und Tests, aber in der Produktion will man asynchrone Transports, die Messages in eine Queue stellen und Worker-Prozessen zur asynchronen Verarbeitung überlassen.

RabbitMQ über AMQP ist der zuverlässigste Transport für produktive Event-Driven Architecture-Setups. AMQP unterstützt Exchanges, Routing-Keys und Queue-Bindings, die sich direkt über die Symfony-Konfiguration abbilden lassen. Redis Streams sind leichter aufzusetzen und ausreichend für moderate Lastszenarien. Doctrine als Transport ist eine pragmatische Wahl für Teams ohne dedizierte Message-Broker-Infrastruktur: Die Messages landen in einer Datenbanktabelle, Worker-Prozesse pollen und verarbeiten sie. Der Nachteil ist, dass die Datenbank unter hoher Last ein Bottleneck wird. Für Einstiegsprojekte mit geringem Message-Volumen ist Doctrine aber absolut legitim und erfordert keine zusätzliche Infrastruktur.


# config/packages/messenger.yaml
# Symfony Messenger transport and routing configuration

framework:
  messenger:
    # Multiple buses: command, query and event bus with individual middlewares
    default_bus: command.bus

    buses:
      command.bus:
        middleware:
          - App\Messenger\Middleware\CommandLoggingMiddleware
      query.bus:
        middleware: []
      event.bus:
        default_middleware:
          enabled: true
          allow_no_handlers: true  # Events may have zero handlers

    transports:
      # Async AMQP transport for order-related messages
      orders_async:
        dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
        options:
          exchange:
            name: orders
            type: topic
          queues:
            orders_high_priority:
              binding_keys: ['order.#']

      # Redis Streams transport for notification events
      notifications_async:
        dsn: 'redis://localhost:6379/messages'
        options:
          stream: notifications
          group: symfony

      # Doctrine-backed failure transport — stores failed messages for retry
      failed:
        dsn: 'doctrine://default?queue_name=failed'

    routing:
      # Commands routed to orders queue
      App\Order\Application\Command\PlaceOrderCommand: orders_async
      App\Inventory\Application\Command\ReserveStockCommand: orders_async

      # Events routed to notifications queue
      App\Order\Domain\Event\OrderPlacedEvent: notifications_async
      App\Customer\Domain\Event\CustomerRegisteredEvent: notifications_async

    failure_transport: failed

5. Routing: welche Message geht wohin?

Das Routing in Symfony Messenger bestimmt, über welchen Transport eine Message dispatcht wird. Die Konfiguration unter framework.messenger.routing mappt Message-Klassen auf Transport-Namen. Ohne Routing-Eintrag wird die Message synchron verarbeitet. Das ist ein bewusster Default: Nicht alle Messages müssen asynchron sein. Queries sollten immer synchron verarbeitet werden, weil der Aufrufer das Ergebnis unmittelbar braucht. Commands und Events können je nach Priorität und Abhängigkeit synchron oder asynchron geroutet werden.

Ein häufiges Pattern in Event-Driven Architecture-Projekten ist die Verwendung mehrerer Transports mit unterschiedlichen Prioritäten. Kritische Commands wie Zahlungsverarbeitung landen in einer High-Priority-Queue mit wenig Parallelität aber sofortiger Verarbeitung. Weniger zeitkritische Events wie Statistik-Aktualisierungen landen in einer Low-Priority-Queue, die mit Verzögerung verarbeitet wird. Symfony Messenger ermöglicht mehrere Worker-Prozesse pro Transport und mehrere Transports im gleichen Worker-Aufruf: php bin/console messenger:consume orders_async notifications_async --limit=500 verarbeitet beide Transports in einem Prozess und beendet sich nach 500 Messages — ideal für Supervisor oder systemd.

6. Middlewares: Logging, Tracing und Idempotenz

Middlewares in Symfony Messenger sind Klassen, die jeden Message-Dispatch umschließen und dabei Querschnittsbelange implementieren. Eine Logging-Middleware schreibt den Message-Typ und die Verarbeitungsdauer in das Application-Log. Eine Tracing-Middleware erstellt OpenTelemetry-Spans, die den gesamten Weg einer Message durch das System nachvollziehbar machen — vom HTTP-Request über den Command-Bus in die async Queue bis zum Worker. Eine Idempotenz-Middleware prüft anhand einer Message-ID, ob eine Message schon verarbeitet wurde, und überspringt sie bei Duplikaten. Das ist besonders wichtig bei Retry-Szenarien, wo eine Message mehrfach zugestellt werden kann.

Die Built-in Middlewares von Symfony Messenger übernehmen Doctrine-Transaktionen automatisch: Die DoctrineTransactionMiddleware wrappet jeden Handler in eine Transaktion, die bei Erfolg committed und bei Exception zurückgerollt wird. Das verhindert, dass eine halbfertig verarbeitete Bestellung in der Datenbank landet. Die ValidationMiddleware validiert die Message gegen Symfony-Validator-Constraints, bevor der Handler aufgerufen wird — fehlerhafte Input-Daten werden abgewiesen, bevor sie in einer Queue landen. Middlewares werden als Services konfiguriert und in der Bus-Definition referenziert.


<?php

declare(strict_types=1);

namespace App\Messenger\Middleware;

use Psr\Log\LoggerInterface;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
use Symfony\Component\Messenger\Middleware\StackInterface;
use Symfony\Component\Messenger\Stamp\HandledStamp;

/**
 * Middleware that logs every message dispatch with duration and result.
 */
final readonly class CommandLoggingMiddleware implements MiddlewareInterface
{
    public function __construct(
        private LoggerInterface $logger,
    ) {}

    /**
     * Log the message class, duration and handler result.
     */
    public function handle(Envelope $envelope, StackInterface $stack): Envelope
    {
        $messageClass = $envelope->getMessage()::class;
        $startTime = hrtime(true);

        $this->logger->info('Dispatching message', ['message' => $messageClass]);

        try {
            $envelope = $stack->next()->handle($envelope, $stack);

            $durationMs = (hrtime(true) - $startTime) / 1_000_000;

            // Check if handled synchronously (HandledStamp is only added for sync handling)
            $handledStamp = $envelope->last(HandledStamp::class);

            $this->logger->info('Message handled successfully', [
                'message'     => $messageClass,
                'duration_ms' => round($durationMs, 2),
                'handled_by'  => $handledStamp?->getHandlerName() ?? 'async transport',
            ]);

            return $envelope;
        } catch (\Throwable $e) {
            $durationMs = (hrtime(true) - $startTime) / 1_000_000;
            $this->logger->error('Message handling failed', [
                'message'     => $messageClass,
                'duration_ms' => round($durationMs, 2),
                'error'       => $e->getMessage(),
            ]);
            throw $e;
        }
    }
}

7. Retry, Failure-Transport und Dead-Letter-Queue

Netzwerkfehler, temporäre Datenbankausfälle und externe API-Timeouts sind in asynchronen Systemen unvermeidlich. Symfony Messenger hat eine eingebaute Retry-Strategie, die fehlgeschlagene Messages nach konfigurierbarer Wartezeit erneut zustellt. Die Standard-Strategie ist exponentielles Backoff: nach dem ersten Fehler 1 Sekunde warten, nach dem zweiten 2 Sekunden, nach dem dritten 4 Sekunden — bis zur konfigurierten maximalen Anzahl von Versuchen. Nach dem letzten gescheiterten Versuch landet die Message im Failure-Transport, der separat konfiguriert wird und als Dead-Letter-Queue fungiert.

Der Failure-Transport speichert fehlgeschlagene Messages zusammen mit dem Fehlerkontext: Exception-Klasse, Stack-Trace, Anzahl der Versuche und Zeitstempel. Mit dem Befehl php bin/console messenger:failed:show zeigt man alle gescheiterten Messages an. php bin/console messenger:failed:retry 42 stellt eine spezifische Message erneut zu. Für das Monitoring von Failure-Queues eignen sich Tools wie Datadog, Grafana mit Prometheus oder ein einfaches Dashboard, das die Tabelle des Doctrine-Failure-Transports auswertet. Wichtig: Nicht jeder Fehler ist ein temporärer. Symfony Messenger erlaubt es, bestimmte Exception-Typen von der Retry-Logik auszuschließen — ein Validierungsfehler aufgrund ungültiger Input-Daten wird sich auch nach zehn Retries nicht beheben.

8. Domain Events aus Doctrine Entities dispatchen

Domain Events sind das Herzstück einer echten Event-Driven Architecture: Eine Entity verwaltet intern eine Liste von aufgezeichneten Events und gibt sie nach dem erfolgreichen Persistieren an den Symfony Messenger-Bus weiter. Das Pattern heißt "Outbox Pattern" oder "Collected Events": Die Entity ruft kein externe Service auf, sie notiert nur, was passiert ist. Der Dispatcher — typischerweise ein Doctrine EventSubscriber — sammelt nach dem Flush alle Entities mit pending Events, dispatcht sie und leert die Liste.

Dieses Pattern löst ein klassisches Problem der Event-Driven Architecture: Wenn das Event vor dem Flush dispatcht wird und der Flush danach fehlschlägt, haben Handler reagiert, aber der Zustandswechsel ist nicht persistiert. Umgekehrt: Wenn das Flush erfolgreich ist und das Dispatchen danach fehlschlägt, wurde der Zustand geändert, aber niemand hat reagiert. Das Dispatchen im postFlush-Hook von Doctrine garantiert: Events werden nur nach erfolgreichem Persistieren an Symfony Messenger übergeben. Für absolute Zuverlässigkeit ergänzt man das mit dem Outbox-Pattern in der Datenbank — aber für die meisten Anwendungsfälle reicht der PostFlush-Ansatz.


<?php

declare(strict_types=1);

namespace App\Shared\Domain;

// Trait for recording domain events — add to any Doctrine Entity
trait RecordsEvents
{
    /** @var list<object> */
    private array $recordedEvents = [];

    /**
     * Record a domain event to be dispatched after successful persistence.
     */
    protected function recordEvent(object $event): void
    {
        $this->recordedEvents[] = $event;
    }

    /**
     * Pull and clear all recorded events.
     *
     * @return list<object>
     */
    public function releaseEvents(): array
    {
        $events = $this->recordedEvents;
        $this->recordedEvents = [];
        return $events;
    }
}

// Doctrine Event Subscriber — dispatches domain events after successful flush
namespace App\Shared\Infrastructure\Doctrine;

use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
use Doctrine\ORM\Event\PostFlushEventArgs;
use Doctrine\ORM\Events;
use App\Shared\Domain\RecordsEvents;
use Symfony\Component\Messenger\MessageBusInterface;

#[AsDoctrineListener(Events::postFlush)]
final class DomainEventDispatcher
{
    public function __construct(
        private readonly MessageBusInterface $eventBus,
    ) {}

    /**
     * Collect domain events from all managed entities and dispatch them via the event bus.
     */
    public function postFlush(PostFlushEventArgs $args): void
    {
        $unitOfWork = $args->getObjectManager()->getUnitOfWork();

        foreach ($unitOfWork->getIdentityMap() as $entityClass => $entities) {
            foreach ($entities as $entity) {
                if (!method_exists($entity, 'releaseEvents')) {
                    continue;
                }

                foreach ($entity->releaseEvents() as $event) {
                    $this->eventBus->dispatch($event);
                }
            }
        }
    }
}

9. Symfony Messenger vs. direkte Service-Aufrufe

Der direkte Vergleich zwischen direkten Service-Aufrufen und Symfony Messenger zeigt, wo der Message Bus echte Vorteile bringt und wo er Overhead erzeugt. Die Entscheidung hängt von Systemkomplexität, Skalierungsanforderungen und Team-Erfahrung ab.

Aspekt Direkte Service-Aufrufe Symfony Messenger Empfehlung
Entkopplung Direktabhängigkeit zwischen Services Handler kennen sich nicht Messenger bei wachsender Komplexität
Async-Verarbeitung Manuell: Queues, Worker, Retry Transport + Worker eingebaut Messenger, wenn Async nötig
Debugging Einfacher Stack-Trace Failure-Transport, Stamps prüfen Direktaufruf bei einfachen Services
Testbarkeit Mock des aufgerufenen Service InMemoryTransport + assertDispatched Beide gut testbar
Retry bei Fehlern Manuell implementieren Exponentielles Backoff eingebaut Messenger für externe Calls

Die Tabelle zeigt: Symfony Messenger gewinnt deutlich bei asynchroner Verarbeitung, Retry-Logik und Entkopplung. Für einfache Synchron-Operationen, die immer im selben Request laufen und keine Retry-Semantik brauchen, ist ein direkter Service-Aufruf klarer und einfacher zu debuggen. Viele Symfony-Projekte nutzen beides: Messenger für Commands, die Seiteneffekte haben und asynchron erledigt werden können, und direkte Aufrufe für Services, die im selben Request ausgeführt werden müssen.

Mironsoft

Symfony Messenger, Event-Driven Architecture und skalierbare Backend-Systeme

Event-Driven Architecture mit Symfony Messenger aufbauen?

Wir entwerfen und implementieren Event-Driven Architectures mit Symfony Messenger — von der Bus-Konfiguration über Domain Events und Transports bis zur vollständigen Monitoring-Infrastruktur für euren Stack.

Architektur-Design

Command/Query/Event-Trennung, Bus-Konfiguration und Transport-Strategie für euer Projekt

Domain Events

Outbox-Pattern, Doctrine-Integration und zuverlässige Event-Zustellung auch bei Systemausfällen

Monitoring

Failure-Queue-Dashboard, Retry-Strategien und Alerting für gescheiterte Messages einrichten

10. Zusammenfassung

Symfony Messenger ist die Basis für eine produktionsreife Event-Driven Architecture in PHP. Die klare Trennung in Commands, Queries und Events — jeder mit eigenen semantischen Regeln und eigenem Bus — schafft Klarheit im Code und Flexibilität in der Skalierung. Transports abstrahieren den Broker dahinter: ob RabbitMQ, Redis oder Doctrine, der Handler-Code bleibt identisch. Middlewares kapseln Querschnittsbelange wie Logging, Tracing und Idempotenz einmalig für alle Messages. Retry-Strategien und Failure-Transports machen das System widerstandsfähig gegenüber temporären Fehlern.

Domain Events aus Doctrine Entities mit dem PostFlush-Dispatcher zu verbinden ist das Muster, das Zustandswechsel und ihre Konsequenzen konsistent hält. Neue Anforderungen werden durch neue Handler implementiert, ohne bestehenden Code anzufassen. Tests profitieren vom InMemoryTransport, der ohne externe Queue-Infrastruktur prüft, ob die richtigen Messages dispatcht wurden. Symfony Messenger ist damit nicht nur ein technisches Werkzeug, sondern ein Architektur-Pattern, das Systeme wartbarer, testbarer und skalierbarer macht.

Symfony Messenger & Event-Driven Architecture — Das Wichtigste auf einen Blick

Drei Bus-Typen

Command-Bus (1 Handler), Query-Bus (Rückgabewert), Event-Bus (0–n Handler) separat konfigurieren — schafft Klarheit über Intentionen.

Async Transports

RabbitMQ, Redis Streams oder Doctrine als Queue-Backend — Routing in messenger.yaml bestimmt, welche Message welchen Transport nutzt.

Retry & Failure

Exponentielles Backoff eingebaut. Failure-Transport als Dead-Letter-Queue. messenger:failed:retry für manuelle Wiederholung.

Domain Events

RecordsEvents-Trait in Entity + PostFlush-Dispatcher garantiert: Events nur nach erfolgreichem Persist dispatchen — konsistente Systemzustände.

11. FAQ: Symfony Messenger und Event-Driven Architecture

1Was ist Symfony Messenger?
Symfony Messenger ist eine Komponente für Message-Bus-basierte Kommunikation — Commands, Queries und Events werden an Handler weitergeleitet, synchron oder asynchron über konfigurierbare Transports.
2Command vs. Query vs. Event?
Commands: Absicht, ein Handler. Queries: Daten lesen, Rückgabewert. Events: was passiert ist, null bis n Handler. Die Trennung ermöglicht CQRS und klare Systemarchitektur.
3Welchen Transport wählen?
RabbitMQ für High-Load, Redis Streams für moderate Last, Doctrine für einfache Setups ohne externe Infrastruktur. Transport wechseln erfordert nur Konfiguration — Handler-Code bleibt identisch.
4Wie funktioniert Retry?
Exponentielles Backoff: Wartezeit verdoppelt sich nach jedem Fehler. Maximale Versuche konfigurierbar. Nach dem letzten Fehlversuch landet die Message im Failure-Transport.
5Was ist der Failure-Transport?
Dead-Letter-Queue für endgültig gescheiterte Messages. messenger:failed:show zeigt sie an, messenger:failed:retry stellt sie erneut zu. Unverzichtbar für Produktions-Monitoring.
6Wie teste ich Messenger-Code?
InMemoryTransport sammelt Messages ohne echte Queue. In Symfony-Tests: $this->transport('async')->queue() prüft, ob die richtigen Messages dispatcht wurden.
7Was sind Domain Events?
Zustandswechsel in der Domain — gesammelt in der Entity, nach erfolgreichem Doctrine-Flush über den Event-Bus dispatcht. Garantiert Konsistenz zwischen Persistenz und Reaktion.
8Mehrere Handler für ein Event?
Ja, wenn der Bus mit allow_no_handlers: true konfiguriert ist. Für Command-Busse ist nur ein Handler erlaubt — ein zweiter löst eine Exception aus.
9Worker-Prozesse starten?
php bin/console messenger:consume transport_name. Supervisor oder systemd für Produktion. --limit=500 beendet nach 500 Messages zur Memory-Leak-Prävention.
10Stamp vs. Envelope?
Envelope ist der Container mit Message und Stamps. Stamps sind Metadaten-Objekte: DelayStamp, RedeliveryStamp, TransportMessageIdStamp. Middlewares lesen und schreiben Stamps für Kontext zwischen Verarbeitungsstufen.