Messenger transports often connect your Symfony application to other applications, which may not use Symfony or even PHP. For example, a shop publishes its orders to RabbitMQ and an invoicing service written in Go consumes them. Symfony 8.2 adds a new serializer for these shared transports and lets all applications keep the identity of each message.
Exchanging Messages with Other Applications
Messenger's Symfony serializer includes envelope stamps alongside the message. This works well when the sending and receiving applications share the same configuration, but can cause problems when they don't.
For example, the receiving application can't decode a stamp whose class isn't
installed. A BusNameStamp can also tell its worker to use a bus that
doesn't exist there, causing an error.
Symfony 8.2 adds the messenger.transport.interop_serializer service. It
sends messages without their stamps and ignores the stamps of the messages
it receives. Use it in all the Symfony applications that share the transport:
1 2 3 4 5 6 7
# config/packages/messenger.yaml
framework:
messenger:
transports:
orders:
dsn: '%env(ORDERS_TRANSPORT_DSN)%'
serializer: messenger.transport.interop_serializer
Messages sent back to the transport for retries, or sent to the failure transport, keep their stamps so Messenger can count retries. The messages sent by this serializer only contain a JSON body and a few plain headers, so applications written in any language can read them.
By default, the type header contains the PHP class of the message, which
means nothing to the other application. Give that class a
serialized type name such as
#[AsMessage(serializedTypeName: 'shop.order_placed')]. If the other
application also uses Messenger, give its class the same name.
If the application sending the messages doesn't use Messenger (e.g. a Python or
Node.js service), its messages may have no type header. When a transport
carries only one kind of message, you can configure its class (or serialized
type name) explicitly:
1 2 3 4 5 6 7
# config/services.yaml
services:
app.orders_serializer:
class: Symfony\Component\Messenger\Transport\Serialization\InteropSerializer
arguments:
- '@messenger.transport.symfony_serializer'
- 'App\Message\OrderPlaced'
Then configure the transport to use this serializer:
1 2 3 4 5 6 7
# config/packages/messenger.yaml
framework:
messenger:
transports:
orders:
dsn: '%env(ORDERS_TRANSPORT_DSN)%'
serializer: app.orders_serializer
If the transport carries several kinds of messages, pass a closure instead of
a class name. The closure receives the message body and headers and returns
the message type, or null if it can't determine the type.
Sharing Stamps as Plain Headers
Removing all stamps also removes some useful information. The original message ID, for example, lets the receiving application recognize the same message when the broker delivers it again. Without that ID, each delivery gets a new one.
Symfony 8.2 adds stamp header mappers to share this information as plain headers that any application can read and set. This preserves useful metadata without sending the PHP stamp objects themselves.
By default, the MessageIdStamp, CorrelationStamp and CausationStamp
are sent as the message_id, correlation_id and causation_id headers.
Messenger adds these stamps when you enable the identity_stamps option.
You can also share information from your own stamps. For example, to include
a tenant ID in each message, implement StampHeaderMapperInterface.
With autoconfiguration enabled, the messenger.transport.interop_serializer
service picks up your mapper automatically:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
// src/Messenger/TenantStampHeaderMapper.php
namespace App\Messenger;
use App\Messenger\Stamp\TenantStamp;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\Transport\Serialization\StampHeaderMapperInterface;
class TenantStampHeaderMapper implements StampHeaderMapperInterface
{
public function toHeaders(Envelope $envelope): array
{
return ($stamp = $envelope->last(TenantStamp::class)) ? ['tenant_id' => $stamp->tenantId] : [];
}
public function toStamps(array $headers): array
{
// these headers come from another application, so check their values
return \is_string($tenantId = $headers['tenant_id'] ?? null) ? [new TenantStamp($tenantId)] : [];
}
}
Signing Messages with Custom Headers
If your transport signs its messages, Symfony 8.2 includes custom headers
in the signature too. The signature covers every header written by the
serializer, except Content-Type, including headers added by stamp header
mappers. Headers added after serialization are ignored.
Caution
Symfony 7.4.18+ and 8.1.6+ refuse signed messages with headers other than
type, Content-Type and X-Message-Stamp-*. If your serializer
writes such headers, upgrade the applications that consume a signing
transport before the ones that send messages to it.