veilletech.fr
22 sept. Feed du jour
#02 SYMFONY Article

Symfony 8.2 : Messenger gagne outbox et claim check

Deux motifs que tout le monde réécrivait à la main. Symfony les livre, avec leurs limites écrites.

Symfony 8.2 ajoute à Messenger deux options de transport. `outbox` écrit le message en base dans la transaction de l'application, puis un relais le transmet au broker. `claim_check` range les messages trop gros dans un pool de cache PSR-6 et n'envoie qu'une référence vérifiée par somme de contrôle.

3 min de lectureintermédiairevidéo 1:20
Partager
Sommaire4 sections
  1. Ce qui se passe
  2. L'outbox : le message entre dans la transaction
  3. Le claim check : le gros message reste au vestiaire
  4. À retenir

Ce qui se passe

Deux défauts classiques de la messagerie asynchrone trouvent une réponse native dans Symfony 8.2. D'une part, écrire en base et publier sur un broker (AMQP, Amazon SQS…) ne forme pas une opération atomique : une transaction annulée laisse un message déjà envoyé, un broker indisponible laisse des données enregistrées sans leur message. D'autre part, les brokers refusent les messages au-delà d'une certaine taille. Messenger reçoit une option de transport pour chacun : outbox, contribuée par Nicolas Grekas, et claim_check, par Yanick Witschi.

L'outbox : le message entre dans la transaction

Jusqu'ici, le contournement consistait à router le message vers le transport Doctrine pour que son insertion rejoigne la transaction — au prix d'une base qui devient la file interrogée par tous les workers. Le motif transactional outbox garde l'écriture transactionnelle mais confie la suite à un worker dédié, le relais, qui relit les messages stockés et les pousse vers le vrai broker. Les consommateurs continuent de lire le broker.

ConsommateurBrokerRelaisBase (outbox)ApplicationConsommateurBrokerRelaisBase (outbox)ApplicationCommit entités + message1Lit les messages stockés2Transmet au transport cible3Consomme et traite4
Le message part avec la transaction, puis le relais le livre au broker
config/packages/messenger.yamlYAML
framework:
    messenger:
        transports:
            orders:
                dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
                outbox: db_outbox
            db_outbox: 'doctrine://default?queue_name=outbox'
Terminal
php bin/console messenger:consume db_outbox   # le relais, sans handler
php bin/console messenger:consume orders      # les handlers, comme avant

Quatre règles à connaître :

Le claim check : le gros message reste au vestiaire

Plutôt que de réduire les messages, le motif claim check dépose ceux qui dépassent un seuil dans un stockage à part et n'envoie qu'un ticket. Le réglage est par transport, puisque chaque broker a sa propre limite.

config/packages/messenger.yamlYAML
framework:
    cache:
        pools:
            messenger.claim_check.cache:
                adapter: cache.adapter.redis
                default_lifetime: 604_800 # 7 jours
    messenger:
        transports:
            async:
                dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
                claim_check:
                    cache_pool: messenger.claim_check.cache
                    max_size: 200_000

Sous max_size (en octets, corps et en-têtes encodés), rien ne change. Au-delà, le message va dans le pool et le transport ne porte qu'un identifiant aléatoire accompagné d'une somme de contrôle, vérifiée à la consommation. N'importe quel pool PSR-6 convient : Redis, Valkey, Memcached, PDO, Doctrine DBAL.

Le pool fait désormais partie de la livraison, ce qui impose trois précautions : qu'il soit joignable par tous les producteurs et consommateurs ; qu'il ne soit pas partagé avec le reste de l'application, car le vider efface les messages en attente ; que son default_lifetime dépasse la vie complète d'un message, délais, retries et séjour dans le transport d'échec compris. Un ticket expiré lève une ClaimCheckNotFoundException, enveloppée dans une MessageDecodingFailedException, et suit le chemin normal des retries.

Source : New in Symfony 8.2: Messenger Outbox and Claim Check, blog Symfony, 21 septembre 2026.