Consumer Error Handling in Symfony Messenger / RabbitMQ - symfony

I'm using the new Symfony Messenger Component 4.1 and RabbitMQ 3.6.10-1 to queue and asynchronously send email and SMS notifications from my Symfony 4.1 web application. My Messenger configuration (messenger.yaml) looks like this:
framework:
messenger:
transports:
amqp: '%env(MESSENGER_TRANSPORT_DSN_NOTIFICATIONS)%'
routing:
'App\NotificationBundle\Entity\NotificationQueueEntry': amqp
When a new notification is to be sent, I queue it like this:
use Symfony\Component\Messenger\MessageBusInterface;
// ...
$notificationQueueEntry = new NotificationQueueEntry();
// [Set notification details such as recipients, subject, and message]
$this->messageBus->dispatch($notificationQueueEntry);
Then I start the consumer like this on the command line:
$ bin/console messenger:consume-messages
I have implemented a SendNotificationHandler service where the actual delivery happens. The service configuration:
App\NotificationBundle\MessageHandler\SendNotificationHandler:
arguments:
- '#App\NotificationBundle\Service\NotificationQueueService'
tags: [ messenger.message_handler ]
And the class:
class SendNotificationHandler
{
public function __invoke(NotificationQueueEntry $entry): void
{
$this->notificationQueueService->sendNotification($entry);
}
}
Until this point, everything works smoothly and the notifications get delivered.
Now my question: It may happen that an email or SMS cannot be delivered due to a (temporary) network failure. In such a case, I would like my system to retry the delivery after a specified amount of time, up to a specified maximum number of retries. What is the way to go to achieve this?
I have read about Dead Letter Exchanges, however, I could not find any documentation or example on how to integrate this with the Symfony Messenger Component.

What you need to do is tell RabbitMQ, that the message is rejected instead of acknowledged. By default the messenger will take care of this inside the AmqpReceiver. As you can see there, if you throw an exception that implements the RejectMessageExceptionInterface inside your handler, the message will automatically be rejected for you.
You could also "simulate" this behaviour with custom middleware. I created something like it, in a small demo application. The mechanism consists of a middleware that wraps the (serialized) original message inside a new RetryMessage and sends it via a custom message bus to a different queue, used as a dead letter exchange. The handler for that message will then unpack the RetryMessage (getting the original message and deserializing it) and transmit it over the default bus:
See:
RetryMessage
RetryMiddleware
messenger.yaml
RetryMessageHandler
This is a basic setup which rejects the message and allows you to consume it again instantly(!). You probably want to add additional information such as headers for timestamps when delaying the consumption to improve on this. For this you should look at writing your own receiver, middleware and/or handler.

Related

Azure Service Bus interoperability between Apache Camel based producer and .NET consumer

we're trying to build an event-based integration between an Apache Camel based system with produces messages in an Azure Service Bus topic and an .NET based consumer of these messages.
The producer used the AMQP interface of the Service Bus, while the .NET based consumer uses the current API from Microsoft in namespace Azure.Messaging.ServiceBus.
When we try to access the body in a received message as follows:
private async Task ProcessMessagesAsync(ProcessMessageEventArgs args)
{
try {
message = Encoding.UTF8.GetString(args.Message.Body);
}
catch( Exception e)
{
_logger.LogError(e, "Body not decoded: Message: {#message}", e.Message);
}
_logger.LogInformation("Body Type: {#bodytype}, Content-Type: {#contenttype}, Message: {#message}, Properties: {#properties}", raw.Body.BodyType, args.Message.ContentType, message, args.Message.ApplicationProperties);
await args.CompleteMessageAsync(args.Message);
}
the following exception is raised:
Value cannot be retrieved using the Body property.Use GetRawAmqpMessage to access the underlying Amqp Message object.
System.NotSupportedException: Value cannot be retrieved using the Body property.Use GetRawAmqpMessage to access the underlying Amqp Message object.
at Azure.Messaging.ServiceBus.Amqp.AmqpMessageExtensions.GetBody(AmqpAnnotatedMessage message)
at Azure.Messaging.ServiceBus.ServiceBusReceivedMessage.get_Body()
When peeking the topic with service bus explorer the message looks strange:
#string3http://schemas.microsoft.com/2003/10/Serialization/�_{"metadata":{"version":"1.0.0","message_id":"AGHcNehoD-hK0pPJCSga9v9sXFwC","message_timestamp":"2022-01-10T13:34:32.778Z"},"data":{"source_timestamp":"2022-01-05T17:20:31.000","material":"101052"}}
When messages are sent to another topic with a .NET producer there's a plaintext JSON body in the topic, as expected.
Did anybody successfully build a solution with Azure Service Bus with components based on the two mentioned frameworks, and what did the trick so that interoperability did work? Who can a Camel AMQP producer create messages with a BodyType of Data so that the body can be decoded by the .NET Service Bus client libraries without need to use GetRawAmqpMessage?
I can't speak to what format Camel is using, but that error message indicates that the Body that you're trying to decode is not an AMQP data body, which is what the Service Bus client library uses and expects.
In order to read a body that is encoded as an AMQP value or sequence, you'll need to work with the data in AMQP format rather than by using the ServiceBusReceivedMessage convenience layer. To do so, you'll want to call GetRawAmqpMessage on the ServiceBusReceivedMessage, which will give you back an AmqpAnnotatedMessage.
The annotated message Body property will return an AmqpMessageBody instance which will allow you to query the BodyType and retrieve the data in its native format using one of the TryGetmethods on the AmqpMessageBody.
On our procuder side a SAP Cloud Integration is used, when the Message Type parapeter of the AMQP Adapter is set to Binary, according to:
https://help.sap.com/viewer/368c481cd6954bdfa5d0435479fd4eaf/Cloud/en-US/d5660c146a93483692335e9d79a8c58f.html.
This seem to correspond to Apache Camel jmsMessageType set to Bytes,
see https://camel.apache.org/components/3.14.x/amqp-component.html for details.
The decoding of the body in the ServiceBusReceivedMessage works as expected and the BodyType is set to Data. If using Text on the producer side, the BodyType will be set to Value as described which led to the problems with the decoding of the body.

How to send mail using different queues in new Symfony Mail component

I want to configure Symfony Mail component to send different types of emails using different queue priorities.
How can I do it?
So far I configured the queues:
framework:
messenger:
transports:
async_low: '%env(MESSENGER_TRANSPORT_DSN_LOW)%'
async_high: '%env(MESSENGER_TRANSPORT_DSN_HIGH)%'
routing:
'Symfony\Component\Mailer\Messenger\SendEmailMessage': async_high
Then I started the queue with async_high being processed first:
bin/console messenger:consume async_high async_low
Now I would like to send one type of email form higher priority queue than the other:
$this->mailer->send($newsletterEmail); // 10.000 times
$this->mailer->send($resetPasswordEmail); // Triggered after newsletter
Without priorities the password reset would be sent after an hour (after all the newsletters get processed).
How can I send the newsletters using the lower priority queue?

Symfony Messenger does not consume messages until stop/consume

On Symfony 5.1, I created two very basic message classes.
The first updates the database and then it calls the other to send a mail via SwiftMailer.
Both of them arrive to the consumer command as they are logged by the bin/console messenger:consume async -vv command, but while the class that updates the database is handled, the other remains suspended.
The fact is that if I stop and restart the consume command, this message becomes handled by the consumer and so the mail is sent.
I have tried with Redis and RabbitMQ transports, with the very same results.
This is the messenger configuration:
framework:
messenger:
transports:
async:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
routing:
'App\Message\FollowRequestMessage': async
'App\Message\EmailMessage': async
This is the MESSENGER_TRANSPORT_DSN in .env:
MESSENGER_TRANSPORT_DSN=amqp://guest:guest#127.0.0.1:5672/%2f/messages
UPDATE
I defined another transport to handle the FollowRequest and Email messages separately, by duplicating the "async" transport config in the yaml messenger configuration.
This has fixed the issue, but I don't know if it the right way to handle the case.
So, it is correct to create a different transport for each kind of message? Or is it a workaround because of an error in something else?

Why alive subscriber get only last published message

M2mqtt incorporate in my asp.net mvc project. Face problem to synch subscribe informations.
When more than one clients published on one specific topic, client can subscribe them easily.
suppose in one situation when published happen then client is down/offline when he alive then only get the last published message not all published messages.
What to do?Is it a problem on MQTT?How alive client get all published messages.
M2mqtt connection with broker use by bellow syntax
public static MqttClient SmartHomeMQTT { get; set; }
SmartHomeMQTT = new MqttClient(brokerAddress, MqttSettings.MQTT_BROKER_DEFAULT_SSL_PORT, true, new X509Certificate(Resource.ca), null, MqttSslProtocols.TLSv1_2, client_RemoteCertificateValidationCallback);
SmartHomeMQTT.Connect("6ea592c5-4b2f-481a-bb0a-eccbe8579d14", "####", "####", false, 3600);
**Note:**Connect method parameter four set to false for clean_session property but it's not work.
To ensure that subscribers receive all messages, even ones that are published when they are offline (known as message persistence), you need to do a few things:
Make sure that 'Clean Session' is turned off in the subscribers
Ensure that each subscriber is using a unique Client ID
Use a QoS of 1 or 2
You don't say which MQTT server you are using, but you need to ensure that the server implementation supports it too.

Can I add elements to the aps payload sent by the Bluemix IBM Push Notifications service?

I'm successfully using Bluemix Push to send notifications via the REST interface to an iOS app with a simple string alert message. That works fine.
Now I would like to send a more complex message where alert is a dictionary and has a sibling "category" element per The Remote Notification Payload.
Is this possible with Bluemix Push? Whenever I try to deviate from the basic structure, I get "Bad Request - Invalid JSON".
After much head-scratching, I finally picked up a hint from https://www.ng.bluemix.net/docs/services/mobilepush/t_advanced_notifications.html#t_push_badge_sound_payload and figured out that since the category field is unique to APNS, what I needed to send is
"settings" : {
"apns" : {
"category" : "myCategory"
}
#DSchultz_mo I was having issues to find the documentation but I finally found it, so if you go to https://mobile.ng.bluemix.net/imfpushrestapidocs/#/ you can use swagger to register your device and send notification and the magic button is in model there is more details for sendMessageBody

Resources