Laravel Queues

Ecotone comes with Laravel Queues integration. This means we can use our Queues as Message Channels for asynchronous communication.

Asynchronous Message Handler

When your Queue for given Connection is set up, you may register it in Ecotone as Asynchronous Message Channel.

We register it using Service Context in Ecotone:

final class MessagingConfiguration
{
    #[ServiceContext]
    public function asyncChannel()
    {
        return LaravelQueueMessageChannelBuilder::create(
            "orders",  // Queue name
            "database" // Optional connection name, otherwise default
        );
    }
}

After that we can start using it as any other asynchronous channel.

#[Asynchronous('orders')]
#[CommandHandler(endpointId:"place_order_endpoint")]
public function placeOrder(PlaceOrder $command): void
{
    // place the order
}

Trigger Command/Event/Query via Ecotone Bus

In order to trigger Command Event or Query Handler, we will be sending the Message via given Ecotone's Bus.

Ecotone provide Command/Event/Query buses. It's important to distinguish between Message types as Queries are handled synchronously, Commands are point to point and target single Handler, Events on other hand are publish-subscribe which means multiple Handlers can subscribe to it.

In order to trigger given Bus, inject CommandBus, EventBus or QueryBus (they are automatically available after installing Ecotone) and make use of them to send a Message.

final class OrderController
{
    public function __construct(private CommandBus $commandBus) {}

    public function placeOrder(Request $request): Response
    {
        $command = $this->prepareCommand($request);

        $this->commandBus->send($command);

        return view('success');
    }
    
    (...)

Running Message Consumer (Worker)

Instead of using queue:work, we will be running Message Consumer using Ecotone's command:

php artisan ecotone:run orders -vvv

In case of failures Ecotone's retry strategy will kick in.

Sending messages via routing

When sending command and events via routing, it's possible to use non-class types.

  • Command Handler with command having array payload

$this->commandBus->sendWithRouting('order.place', ["products" => [1,2,3]]);
#[Asynchronous('orders')]
#[CommandHandler('order.place', 'place_order_endpoint')]
public function placeOrder(array $payload): void
{
    // do something with 
}
  • Command Handler inside Aggregate with command having no payload at all

$this->commandBus->sendWithRouting('order.place', metadata: ["aggregate.id" => 123]);
#[Aggregate]
class Order
{
    #[Asynchronous('orders')]
    #[CommandHandler('cancel', 'cancel_order_endpoint')]
    public function cancel(): void
    {
        $this->isCancelled = true;
    }
(...)    

Asynchronous Event Handlers

In case of sending events, we will be using Event Bus. Ecotone deliver copy of the Event to each of the Event Handlers, this allows for handling in isolation and safe retries.

  • Inject Event Bus into your service, it will be available out of the box.

$this->eventBus->publish(new OrderWasPlaced($orderId));
  • Subscribe to event

#[Asynchronous('orders')]
#[EventHandler('order_was_placed_endpoint')]
public function notifyAbout(OrderWasPlaced $event): void
{
    // send notification
}

Serializing in different formats

By default all your Messages will be serialized using PHP native serialization. However this is not recommended way, as native PHP serialization requires class to be kept the same on deserialization, if we will change the class name, we will fail to deserialize.

We may register our our own different Media Converters, yet you may use inbuilt solution using Ecotone JMS, to serialize to JSON without any additional configuration.

#[ServiceContext]
public function asyncChannel()
{
    return LaravelQueueMessageChannelBuilder::create("orders")
                ->withDefaultOutboundConversionMediaType(MediaType::createApplicationJson());
}

If you're using Ecotone JMS, it will automatically set up all your Message Channels to serialize to JSON, as long as you explicitly not state different format.

Last updated