Bridge to use Symfony Messenger on AWS Lambda with Bref.
This bridge allows messages to be dispatched to SQS, SNS or EventBridge, while workers handle those messages on AWS Lambda.
This guide assumes that:
- Symfony is installed
- Symfony Messenger is installed
- Bref is installed and configured to deploy Symfony
First, install this package:
composer require bref/symfony-messenger
Next, register the bundle in config/bundles.php
:
return [
// ...
Bref\Symfony\Messenger\BrefMessengerBundle::class => ['all' => true],
];
SQS, SNS and EventBridge can now be used with Symfony Messenger.
Symfony Messenger dispatches messages. To create a message, follow the Symfony Messenger documentation.
To configure where messages are dispatched, all the examples in this documentation are based on the example from the Symfony documentation:
# config/packages/messenger.yaml
framework:
messenger:
transports:
async: '%env(MESSENGER_TRANSPORT_DSN)%'
routing:
'App\Message\MyMessage': async
The SQS service is a queue that works similar to RabbitMQ. To use it, create a SQS queue and set its URL as the DSN:
MESSENGER_TRANSPORT_DSN=https://sqs.us-east-1.amazonaws.com/123456789/my-queue
That's it, messages will be dispatched to that queue.
Note: when running Symfony on AWS Lambda, it is not necessary to configure credentials. The AWS client will read them from environment variables automatically.
To consume messages from SQS:
- Create the function that will be invoked by SQS in
serverless.yml
:
functions:
worker:
handler: bin/consumer.php
timeout: 20 # in seconds
reservedConcurrency: 5 # max. 5 messages processed in parallel
layers:
- ${bref:layer.php-74}
events:
# Read more at https://www.serverless.com/framework/docs/providers/aws/events/sqs/
- sqs:
arn: arn:aws:sqs:us-east-1:1234567890:my_sqs_queue
# Only 1 item at a time to simplify error handling
batchSize: 1
- Create the handler script (for example
bin/consumer.php
):
<?php declare(strict_types=1);
use Bref\Symfony\Messenger\Service\Sqs\SqsConsumer;
require dirname(__DIR__) . '/config/bootstrap.php';
$kernel = new \App\Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
$kernel->boot();
// Return the Bref consumer service
return $kernel->getContainer()->get(SqsConsumer::class);
- Register and configure the
SqsConsumer
service:
# config/services.yaml
services:
Bref\Symfony\Messenger\Service\Sqs\SqsConsumer:
public: true
autowire: true
arguments:
# Pass the transport name used in config/packages/messenger.yaml
$transportName: 'async'
Now, anytime a message is dispatched to SQS, the Lambda function will be called. The Bref consumer class will put back the message into Symfony Messenger to be processed.
The FIFO queue guarantees exactly once delivery. To differentiate messages we must either configure the FIFO queue to look at a specific parameter in the message, or let AWS calculate a hash over the message body. The latter is simpler and it is enabled by using "Content-Based Deduplication".
We also need to specify what message group we are using. It can be your applications reverse hostname.
# config/packages/messenger.yaml
framework:
messenger:
transports:
async:
dsn: 'https://sqs.us-east-1.amazonaws.com/123456789/my-queue.fifo'
options: { message_group_id: com_example }
Everything else is identical to the normal SQS queue.
AWS SNS is "notification" instead of "queues". Messages may not arrive in the same order as sent, and they might arrive all at once. To use it, create a SNS topic and set it as the DSN:
MESSENGER_TRANSPORT_DSN=sns://arn:aws:sns:us-east-1:1234567890:foobar
That's it, messages will be dispatched to that topic.
Note: when running Symfony on AWS Lambda, it is not necessary to configure credentials. The AWS client will read them from environment variables automatically.
To consume messages from SNS:
- Create the function that will be invoked by SNS in
serverless.yml
:
functions:
worker:
handler: bin/consumer.php
timeout: 20 # in seconds
reservedConcurrency: 5 # max. 5 messages processed in parallel
layers:
- ${bref:layer.php-74}
events:
# Read more at https://www.serverless.com/framework/docs/providers/aws/events/sns/
- sns:
arn: arn:aws:sns:us-east-1:1234567890:my_sns_topic
- Create the handler script (for example
bin/consumer.php
):
<?php declare(strict_types=1);
use Bref\Symfony\Messenger\Service\Sns\SnsConsumer;
require dirname(__DIR__) . '/config/bootstrap.php';
$kernel = new \App\Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
$kernel->boot();
// Return the Bref consumer service
return $kernel->getContainer()->get(SnsConsumer::class);
- Register and configure the
SnsConsumer
service:
# config/services.yaml
services:
Bref\Symfony\Messenger\Service\Sns\SnsConsumer:
public: true
autowire: true
arguments:
# Pass the transport name used in config/packages/messenger.yaml
$transportName: 'async'
Now, anytime a message is dispatched to SNS, the Lambda function will be called. The Bref consumer class will put back the message into Symfony Messenger to be processed.
AWS EventBridge is a message routing service. It is similar to SNS, but more powerful. To use it, configure the DSN like so:
# "myapp" is the EventBridge "source", i.e. a namespace for your application's messages
# This source name will be reused in `serverless.yml` later.
MESSENGER_TRANSPORT_DSN=eventbridge://myapp
That's it, messages will be dispatched to EventBridge.
Note: when running Symfony on AWS Lambda, it is not necessary to configure credentials. The AWS client will read them from environment variables automatically.
To consume messages from EventBridge:
- Create the function that will be invoked by EventBridge in
serverless.yml
:
functions:
worker:
handler: bin/consumer.php
timeout: 20 # in seconds
reservedConcurrency: 5 # max. 5 messages processed in parallel
layers:
- ${bref:layer.php-74}
events:
# Read more at https://www.serverless.com/framework/docs/providers/aws/events/event-bridge/
- eventBridge:
# This filters events we listen to: only events from the "myapp" source.
# This should be the same source defined in config/packages/messenger.yaml
pattern:
source:
- myapp
- Create the handler script (for example
bin/consumer.php
):
<?php declare(strict_types=1);
use Bref\Symfony\Messenger\Service\EventBridge\EventBridgeConsumer;
require dirname(__DIR__) . '/config/bootstrap.php';
$kernel = new \App\Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
$kernel->boot();
// Return the Bref consumer service
return $kernel->getContainer()->get(EventBridgeConsumer::class);
- Register and configure the
EventBridgeConsumer
service:
# config/services.yaml
services:
Bref\Symfony\Messenger\Service\EventBridge\EventBridgeConsumer:
public: true
autowire: true
arguments:
# Pass the transport name used in config/packages/messenger.yaml
$transportName: 'async'
Now, anytime a message is dispatched to EventBridge for that source, the Lambda function will be called. The Bref consumer class will put back the message into Symfony Messenger to be processed.
AWS Lambda has error handling mechanisms (retrying and handling failed messages). Because of that, this package does not integrates Symfony Messenger's retry mechanism. Instead, it works with Lambda's retry mechanism.
This section is work in progress, feel free to contribute to improve it.
When a message fails with SQS, by default it will go back to the SQS queue. It will be retried until the message expires. Here is an example to setup retries and "dead letter queue" with SQS:
# serverless.yml
resources:
Resources:
Queue:
Type: AWS::SQS::Queue
Properties:
# This needs to be at least 6 times the lambda function's timeout
# See https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html
VisibilityTimeout: '960'
RedrivePolicy:
deadLetterTargetArn: !GetAtt DeadLetterQueue.Arn
# Jobs will be retried 5 times
# The number needs to be at least 5 per https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html
maxReceiveCount: 5
# The dead letter queue is a SQS queue that receives messages that failed to be processed
DeadLetterQueue:
Type: AWS::SQS::Queue
Properties:
# Messages are stored up to 14 days (the max)
MessageRetentionPeriod: 1209600
When using SNS and EventBridge, messages will be retried by default 2 times.
By default, AWS clients (SQS, SNS, EventBridge) are preconfigured to work on AWS Lambda (thanks to environment variables populated by AWS Lambda).
However, it is possible customize the AWS clients, for example to use them outside of AWS Lambda (locally, on EC2…) or to mock them in tests. These clients are registered as Symfony services under the keys:
bref.messenger.sqs_client
bref.messenger.sns_client
bref.messenger.eventbridge_client
For example to customize the SQS client:
services:
bref.messenger.sqs_client:
class: AsyncAws\Sqs\SqsClient
public: true # the AWS clients must be public
arguments:
# Apply your own config here
-
region: us-east-1
By default, this package registers Symfony Messenger transports for SQS, SNS and EventBridge.
If you want to disable some transports (for example in case of conflict), you can remove BrefMessengerBundle
from config/bundles.php
and reconfigure the transports you want in your application's config. Take a look at Resources/config/services.yaml
to copy the part that you want.
If you want to change how messages are serialized, for example to use Happyr message serializer, you need to add the serializer on both the transport and the consumer. For example:
# config/packages/messenger.yaml
framework:
messenger:
transports:
async:
dsn: 'https://sqs.us-east-1.amazonaws.com/123456789/my-queue'
serializer: 'Happyr\MessageSerializer\Serializer'
# config/services.yaml
services:
Bref\Symfony\Messenger\Service\Sqs\SqsConsumer:
public: true
autowire: true
arguments:
$transportName: 'async'
$serializer: '@Happyr\MessageSerializer\Serializer'