Skip to content

Background tasks

Some operations in Ibexa DXP don’t have to run immediately when a user clicks a button, for example, re-indexing product prices or processing bulk data. Running such operations in real time could slow down the system and disrupt the user experience.

To solve this, Ibexa DXP provides a package called Ibexa Messenger, which is an overlay to Symfony Messenger, and it's job is to queue tasks and run them in the background. Ibexa DXP sends messages (or commands) that represent the work to be done later. These messages are stored in a queue and picked up by a background worker, which ensures that resource-heavy tasks are executed at a convenient time, without putting excessive load on the system.

Ibexa Messenger supports multiple storage backends, such as Doctrine, Redis, and PostgreSQL, and gives developers the flexibility to create their own message handlers for custom use cases.

How it works

Ibexa Messenger uses a command bus as a queue that stores messages, or commands, which tell the system what you want to happen, and separates them from the handler, which is the code that actually performs the task.

The process works as follows:

  1. A message PHP object is dispatched, for example, ProductPriceReindex.
  2. The message is wrapped in an envelope, which may contain additional metadata, called stamps, for example, DeduplicateStamp.
  3. The message is placed in the transport queue. It can be a Doctrine table, a Redis queue, and so on.
  4. A worker process continuously reads messages from the queue, pulls them into the default bus ibexa.messenger.bus and assigns them to the right handler.
  5. A handler service processes the message (executes the command). You can register multiple handlers for different jobs.

Here is an example of how you can extend your code and use Ibexa Messenger to process your tasks:

Configure package

Create a config file, for example, config/packages/ibexa_messenger.yaml and define your transport:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
ibexa_messenger:

    # The DSN of the transport, as expected by Symfony Messenger transport factory.
    transport_dsn:        'doctrine://default?table_name=ibexa_messenger_messages&auto_setup=false'
    deduplication_lock_storage:
        enabled:              true

        # Doctrine DBAL primary connection or custom service
        type:                 doctrine # One of "doctrine"; "custom"; "service"

        # The service ID of a custom Lock Store, if "service" type is selected
        service:              null

        # The DSN of the lock store, if "custom" type is selected
        dsn:                  null

Supported transports

You can define different transports: Ibexa Messenger has been tested to work with Redis, MySQL, PostgreSQL. For more information, see Symfony Messenger documentation or Symfony Messenger tutorial.

Start worker

Use a process manager of your choice to run the following command, or make it start together with the server:

1
php bin/console messenger:consume ibexa.messenger.transport --bus=ibexa.messenger.bus --siteaccess=<OPTIONAL>`

In multi-repository setups, the worker process always works for a SiteAccess that you indicate by using the --siteaccess option, therefore you may need to run multiple workers, one for each SiteAccess.

Multi-repository setups

Doctrine transport works across multiple repositories without issues, but other transports may need to be adjusted, so that queues across different repositories are not accidentally shared.

Deploying Ibexa Messenger

Additional considerations regarding the deployment of Symfony Messenger to production, which you can find in Symfony documentation apply to Ibexa Messenger as well.

Dispatch message

Dispatch a message from your code like in the following example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php declare(strict_types=1);

namespace App\Dispatcher;

use Ibexa\Bundle\Messenger\Stamp\DeduplicateStamp;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\MessageBusInterface;

final class SomeClassThatSchedulesExecutionInTheBackground
{
    private MessageBusInterface $bus;

    public function __construct(MessageBusInterface $bus)
    {
        $this->bus = $bus;
    }

    public function schedule(object $message): void
    {
        // Dispatch directly. Message is wrapped with envelope without any stamps.
        $this->bus->dispatch($message);

        // Alternatively, wrap with stamps. In this case, DeduplicateStamp ensures
        // that if similar command exists in the queue (or is being processed)
        // it will not be queued again.
        $envelope = Envelope::wrap(
            $message,
            [new DeduplicateStamp('command-name-1')]
        );

        $this->bus->dispatch($envelope);
    }
}

Register handler

Create the handler class:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<?php declare(strict_types=1);

namespace App\MessageHandler;

use App\Message\SomeMessage;

final class SomeHandler
{
    public function __invoke(SomeMessage $message): void
    {
        // Handle message.
    }
}

Add a service definition to config/services.yaml:

1
2
3
4
5
services:
    App\MessageHandler\SomeHandler:
        tags:
            - name: messenger.message_handler
              bus: ibexa.messenger.bus