Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions LICENCE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2023 run-as-root

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
182 changes: 181 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,181 @@
Magento 2 message queue retry
# run-as-root/magento2-message-queue-retry

It gives the possibility to process the same queue message more than once,
utilizing The RabbitMQ's [dead letter exchange](https://www.rabbitmq.com/dlx.html) feature.

## Table of Contents
- [Prerequisites](#prerequisites)
- [Installation](#installation)
- [Features](#features)
- [How it works](#how-it-works)
- [Configuration](#configuration)
- [License](#licence)

## Prerequisites

- Magento 2.4.5 or higher
- PHP 8.1 or higher
- RabbitMQ

To be able to use this module, you have to manually configure the dead letter exchange(s) for the queue(s) you want to enable the retry mechanismm through the `queue_topology.xml` file.
An example will be given in the [Configuration](#configuration) section.

Other requisite is that your exchanges have to have a relation from one exchange to only one topic and queue,

For example:

![topology](docs/queue-requirement.png)

## Installation

To install the module via composer:
```bash
composer require run-as-root/magento2-message-queue-retry
```

To enable the module:
```bash
bin/magento module:enable RunAsRoot_MessageQueueRetry
```

## Features

- Toggle activation
- Configure the retry limit for a queue
- Admin grid to manage the messages with the retry limit reached
- Requeue the failed messages to their origin queue
- Delete the message
- Download the message body

## How it works

The default Magento's consumer behavior is to reject the message when an exception is thrown during the consumer's execution.
If you use a standard configuration for the queue (without a dead-letter exchange), the message will be discarded and not processed again.

This behavior will change a bit with this module. It will introduce an extra step that will check if the message has reached your retry limit,
if so, it will be discarded from RabbitMQ and sent to the `run_as_root_message` Mysql table and stay there until manual management through the admin panel.

If the message has not reached the retry limit, it will be rejected, and RabbitMQ will send it to the dead letter exchange. The message will be routed automatically to the "delay" queue and stay there until de TTL time is reached.
After the TTL time is reached, the message will be returned to its original queue.

The diagram below illustrates both approaches:

![img.png](docs/flow.png)

In the admin panel a new grid will be available to manage the messages that have reached the retry limit:

Path: RunAsRoot > Manage Messages

![img.png](docs/messages-grid.png)

The grid colums:

- **Topic name**: The name of the topic that the message belongs to
- **Total retries**: The total number of times the message has been processed
- **Failure Description**: The exception message that was thrown during the processing of the message
- **Message Body**: The original message body, it can be downloaded as a file and it is in JSON format.

The grid actions:

- **Requeue**: It will send the message to the original queue
- **Download**: It will download the message body content as a JSON file

The grid also has a mass action to delete or requeue the selected messages.

Is possible to configure the ACL for each action in the grid and the module configuration:

![img.png](docs/acl.png)

### Configuration

Two steps are necessary to configure the retry for a queue:
1. Configure the dead letter exchange
1. Enable the message queue retry and delclare the retry limit configuration

Let's imagine a scenario that the `erp_order_export` queue already exists in your project and to simplify the example the topic name, exchange name and queue name are the same: `erp_order_export`.

We need to change these two files in order to declare and configure the delay queue:
- `communication.xml`
- `queue_topology.xml`

**The current queue configuration are like this**:

`etc/communication.xml`:

```xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Communication/etc/communication.xsd">
<topic name="erp_order_export" request="string"/>
</config>
```

`etc/queue_topology.xml`:

```xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework-message-queue:etc/topology.xsd">
<exchange name="erp_order_export" connection="amqp" type="topic">
<binding id="erp_order_export" topic="erp_order_export" destinationType="queue" destination="erp_order_export"/>
</exchange>
</config>
```

**You have to change it to**:

`etc/communication.xml`:

```xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Communication/etc/communication.xsd">
<topic name="erp_order_export" request="string"/>
<topic name="erp_order_export_delay" request="string"/>
</config>
```

`etc/queue_topology.xml`:
```xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework-message-queue:etc/topology.xsd">
<exchange name="erp_order_export" connection="amqp" type="topic">
<binding id="erp_order_export" topic="erp_order_export" destinationType="queue" destination="erp_order_export">
<arguments>
<argument name="x-dead-letter-exchange" xsi:type="string">erp_order_export_delay</argument>
<argument name="x-dead-letter-routing-key" xsi:type="string">erp_order_export_delay</argument>
</arguments>
</binding>
</exchange>
<!-- Delay queue -->
<exchange name="erp_order_export_delay" connection="amqp" type="topic">
<binding id="erp_order_export_delay" topic="erp_order_export_delay" destinationType="queue" destination="erp_order_export_delay">
<arguments>
<argument name="x-dead-letter-exchange" xsi:type="string">erp_order_export</argument>
<argument name="x-dead-letter-routing-key" xsi:type="string">erp_order_export</argument>
<argument name="x-message-ttl" xsi:type="number">300000</argument>
</arguments>
</binding>
</exchange>
</config>
```

In the `erp_order_export` exchange binding, we added the `x-dead-letter-exchange` and `x-dead-letter-routing-key` arguments, this will route the message to the `erp_order_export_delay` exchange when the message is rejected.

We added the `erp_order_export_delay` exchange and binding, it points to the original exchange (`erp_order_export`). the `x-message-ttl` argument will configure the period that the message will stay in the `erp_order_export_delay` queue, in this example for 5 minutes (300000ms). When the lifetime expires (TTL), RabbitMQ will send the message to `erp_order_export` automatically.

The `erp_order_export_delay` queue does not have a consumer, it will be used only to hold(delay) messages according with the period defined in the `x-message-ttl` argument.

Now you have to define toggle the activation for the retry queue module and declare the retry limit for the queue:

System > Configuration > RUN-AS-ROOT > Message Queue Retry

![img.png](docs/configuration.png)

Note that if the queue is not declared in the configuration it will the default Magento consumer behavior.

For more information of how to configure message queues in Magento 2, you can take a look [here](https://developer.adobe.com/commerce/php/development/components/message-queues/configuration/).

## License
[MIT](https://opensource.org/licenses/MIT)
Binary file added docs/acl.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/configuration.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/flow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/messages-grid.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/queue-requirement.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 0 additions & 5 deletions src/Block/Adminhtml/QueuesConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,6 @@ protected function _prepareToRender(): void
[ 'label' => __('Main Topic Name'), 'class' => 'required-entry' ]
);

$this->addColumn(
MessageQueueRetryConfig::DELAY_TOPIC_NAME,
[ 'label' => __('Delay Topic Name'), 'class' => 'required-entry' ]
);

$this->addColumn(
MessageQueueRetryConfig::RETRY_LIMIT,
[ 'label' => __('Retry Limit'), 'class' => 'required-entry validate-zero-or-greater' ]
Expand Down
4 changes: 4 additions & 0 deletions src/Ui/Component/Listing/Columns/MessageBody.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ public function prepareDataSource(array $dataSource): array
continue;
}

if (mb_strlen($item['message_body']) < 100) {
continue;
}

$item['message_body'] = substr($item['message_body'], 0, 100) . '...';
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/etc/adminhtml/system.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
<frontend_model>RunAsRoot\MessageQueueRetry\Block\Adminhtml\QueuesConfig</frontend_model>
<backend_model>RunAsRoot\MessageQueueRetry\Model\Config\Backend\QueuesConfig</backend_model>
<comment>
Please, provide the topic name of the delay queue that is within the communication.xml file.
Please, provide the topic name of the queue that is within the communication.xml file.
After the retry limit is reached the message will be sent to the failed messages grid.
</comment>
</field>
Expand Down
7 changes: 3 additions & 4 deletions src/i18n/en_US.csv
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ General,General
default Magento behavior will run instead.","If disabled the queue messages will not be sent to the failed queue messaged grid and the
default Magento behavior will run instead."
"Delay queues","Delay queues"
"Please, provide the topic name of the delay queue that is within the communication.xml file.
After the retry limit is reached the message will be sent to the failed messages grid.","Please, provide the topic name of the delay queue that is within the communication.xml file.
After the retry limit is reached the message will be sent to the failed messages grid."
"Please, provide the topic name of the queue that is within the communication.xml file.
After the retry limit is reached the message will be sent to the failed messages grid.","Please, provide the topic name of the queue that is within the communication.xml file.
After the retry limit is reached the message will be sent to the failed messages grid."
"Admin Configuration","Admin Configuration"
Listing,Listing
Download,Download
Expand All @@ -38,7 +38,6 @@ Requeue,Requeue
ID,ID
"Topic Name","Topic Name"
"Total Retries","Total Retries"
"Resource Id","Resource Id"
"Failure Description","Failure Description"
"Message Body","Message Body"
"Created At","Created At"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,6 @@
<label translate="true">Total Retries</label>
</settings>
</column>
<column name="resource_id">
<settings>
<filter>text</filter>
<label translate="true">Resource Id</label>
</settings>
</column>
<column name="failure_description">
<settings>
<filter>text</filter>
Expand Down