449 lines
10 KiB
Markdown
449 lines
10 KiB
Markdown
# Laravel RabbitMQ Events
|
|
|
|
A package for working with RabbitMQ messages as Laravel events. Automatically serialize and publish events to RabbitMQ, and consume messages from queues by converting them back into events.
|
|
|
|
## Features
|
|
|
|
**Automatic event serialization** — Events implementing the `Broadcast` interface are automatically serialized and sent to RabbitMQ
|
|
**Message consumption** — Command to consume messages from RabbitMQ with automatic deserialization and event dispatching
|
|
**Default bindings** — The `BroadcastEvent` trait sets standard connection parameters for an event
|
|
**Flexible configuration** — Support for multiple connections and parameterization for each event
|
|
**Microservices architecture** — Ideal for data exchange between services
|
|
|
|
## Requirements
|
|
|
|
- PHP 8.1+
|
|
- Laravel 10, 11 or 12
|
|
- RabbitMQ server
|
|
|
|
## Installation
|
|
|
|
Install via Composer:
|
|
|
|
```bash
|
|
composer require diffhead/laravel-rabbitmq
|
|
```
|
|
|
|
The package will be automatically registered thanks to Laravel Service Provider Discovery.
|
|
|
|
## Configuration
|
|
|
|
Publish the configuration file:
|
|
|
|
```bash
|
|
php artisan vendor:publish --provider="Diffhead\PHP\LaravelRabbitMQ\ServiceProvider"
|
|
```
|
|
|
|
ThisEnvironment Variables
|
|
|
|
```env
|
|
# RabbitMQ Connection
|
|
RABBITMQ_HOST=localhost
|
|
RABBITMQ_PORT=5672
|
|
RABBITMQ_USER=guest
|
|
RABBITMQ_PASSWORD=guest
|
|
RABBITMQ_VHOST=/
|
|
|
|
# Default event parameters
|
|
RABBITMQ_EVENT_CONNECTION=default
|
|
RABBITMQ_EVENT_QUEUE=default
|
|
RABBITMQ_EVENT_EXCHANGE=amq.direct
|
|
RABBITMQ_EVENT_EXCHANGE_TYPE=direct
|
|
RABBITMQ_EVENT_EXCHANGE_IS_DEFAULT=true
|
|
RABBITMQ_EVENT_ROUTING_KEY=
|
|
```
|
|
|
|
### Configuration Example
|
|
|
|
```php
|
|
return [
|
|
'connections' => [
|
|
'default' => [
|
|
'host' => env('RABBITMQ_HOST', 'localhost'),
|
|
'port' => env('RABBITMQ_PORT', 5672),
|
|
'user' => env('RABBITMQ_USER', 'guest'),
|
|
'password' => env('RABBITMQ_PASSWORD', 'guest'),
|
|
'vhost' => env('RABBITMQ_VHOST', '/'),
|
|
],
|
|
'secondary' => [
|
|
'host' => env('RABBITMQ_SECONDARY_HOST', 'localhost'),
|
|
'port' => env('RABBITMQ_SECONDARY_PORT', 5672),
|
|
'user' => env('RABBITMQ_SECONDARY_USER', 'guest'),
|
|
'password' => env('RABBITMQ_SECONDARY_PASSWORD', 'guest'),
|
|
'vhost' => env('RABBITMQ_SECONDARY_VHOST', '/'),
|
|
]
|
|
],
|
|
'message' => [
|
|
'serializer' => \Diffhead\PHP\LaravelRabbitMQ\Service\Serializer::class,
|
|
'unserializer' => \Diffhead\PHP\LaravelRabbitMQ\Service\Unserializer::class,
|
|
],
|
|
'event' => [
|
|
'defaults' => [
|
|
'connection' => env('RABBITMQ_EVENT_CONNECTION', 'default'),
|
|
'queue' => env('RABBITMQ_EVENT_QUEUE', 'default'),
|
|
'exchange' => env('RABBITMQ_EVENT_EXCHANGE', 'amq.direct'),
|
|
'exchange_type' => env('RABBITMQ_EVENT_EXCHANGE_TYPE', 'direct'),
|
|
'exchange_is_default' => (bool) env('RABBITMQ_EVENT_EXCHANGE_IS_DEFAULT', true),
|
|
'routing_key' => env('RABBITMQ_EVENT_ROUTING_KEY', ''),
|
|
],
|
|
'mapper' => \Diffhead\PHP\LaravelRabbitMQ\Service\EventMapper::class,
|
|
'map' => [
|
|
/**
|
|
* Map events to queues and routing keys
|
|
*/
|
|
\App\Events\User\UserCreated::class => [
|
|
'queues' => ['portal.users'],
|
|
'routing_keys' => ['user.created'],
|
|
],
|
|
\App\Events\Meeting\MeetingCreated::class => [
|
|
'queues' => ['portal.meetings'],
|
|
'routing_keys' => ['meeting.created'],
|
|
],
|
|
],
|
|
]
|
|
];
|
|
```
|
|
|
|
## Usage
|
|
|
|
### 1. Creating an Event for Publishing to RabbitMQ
|
|
|
|
Create an event that implements the `Broadcast` interface:
|
|
|
|
```php
|
|
namespace App\Events;
|
|
|
|
use Diffhead\PHP\LaravelRabbitMQ\Event\Broadcast;
|
|
use Diffhead\PHP\LaravelRabbitMQ\Trait\BroadcastEvent;
|
|
use Illuminate\Foundation\Events\Dispatchable;
|
|
|
|
class UserCreated implements Broadcast
|
|
{
|
|
use Dispatchable, BroadcastEvent;
|
|
|
|
public function __construct(
|
|
public int $userId,
|
|
public string $email,
|
|
public string $name,
|
|
) {}
|
|
|
|
public function jsonSerialize(): array
|
|
{
|
|
return [
|
|
'userId' => $this->userId,
|
|
'email' => $this->email,
|
|
'name' => $this->name,
|
|
];
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Event Parameters
|
|
|
|
When implementing the `Broadcast` interface, you must define the following methods:
|
|
|
|
- `getConnection(): string` — RabbitMQ connection name
|
|
- `getQueue(): string` — Queue name
|
|
- `getExchange(): string` — Exchange name
|
|
- `getExchangeType(): string` — Exchange type (direct, topic, fanout, headers)
|
|
- `getExchangeIsDefault(): bool` — Whether to use the default exchange
|
|
- `getRoutingKey(): string` — Routing key for the message
|
|
|
|
#### Using the BroadcastEvent Trait
|
|
|
|
The `BroadcastEvent` trait provides implementations of all methods using default parameters from configuration:
|
|
|
|
```php
|
|
namespace App\Events;
|
|
|
|
use Diffhead\PHP\LaravelRabbitMQ\Event\Broadcast;
|
|
use Diffhead\PHP\LaravelRabbitMQ\Trait\BroadcastEvent;
|
|
|
|
class UserCreated implements Broadcast
|
|
{
|
|
use BroadcastEvent;
|
|
|
|
public function __construct(
|
|
public int $userId,
|
|
public string $email,
|
|
) {}
|
|
|
|
public function jsonSerialize(): array
|
|
{
|
|
return [
|
|
'userId' => $this->userId,
|
|
'email' => $this->email,
|
|
];
|
|
}
|
|
}
|
|
```
|
|
|
|
If you need special parameters for a specific event, override the necessary methods:
|
|
|
|
```php
|
|
namespace App\Events;
|
|
|
|
use Diffhead\PHP\LaravelRabbitMQ\Event\Broadcast;
|
|
use Diffhead\PHP\LaravelRabbitMQ\Trait\BroadcastEvent;
|
|
|
|
class CriticalAlert implements Broadcast
|
|
{
|
|
use BroadcastEvent;
|
|
|
|
public function __construct(
|
|
public string $message,
|
|
) {}
|
|
|
|
public function getRoutingKey(): string
|
|
{
|
|
return 'alert.critical';
|
|
}
|
|
|
|
public function getExchange(): string
|
|
{
|
|
return 'alerts.topic';
|
|
}
|
|
|
|
public function getExchangeType(): string
|
|
{
|
|
return 'topic';
|
|
}
|
|
|
|
public function jsonSerialize(): array
|
|
{
|
|
return [
|
|
'message' => $this->message,
|
|
];
|
|
}
|
|
}
|
|
```
|
|
|
|
### 2. Publishing Events
|
|
|
|
Events are automatically published when dispatched:
|
|
|
|
```php
|
|
use App\Events\UserCreated;
|
|
|
|
/**
|
|
* Events implementing Broadcast are automatically sent to RabbitMQ
|
|
*/
|
|
UserCreated::dispatch(userId: 1, email: 'user@example.com', name: 'John Doe');
|
|
```
|
|
|
|
### 3. Consuming Messages from RabbitMQ
|
|
|
|
Use the `rabbitmq:consume` command to listen for messages:
|
|
|
|
```bash
|
|
#####################################################################################
|
|
#
|
|
# Has following options:
|
|
#
|
|
# --connection=default - Connection name from config
|
|
# --queue=default - Queue
|
|
# --exchange=amq.direct - Exchange name
|
|
# --exchange-type=direct - Exchange type
|
|
# --exchange-is-default - Exchange is default, required for default exchanges
|
|
# --routing-key=user.* - Listen routing keys, required for topic exchanges
|
|
# --tag=myconsumer - Consumer tag for rabbitmq
|
|
#
|
|
#####################################################################################
|
|
|
|
php artisan rabbitmq:consume
|
|
```
|
|
#### Full Consumer Startup Example
|
|
|
|
```bash
|
|
php artisan rabbitmq:consume \
|
|
--connection=default \
|
|
--queue=service.users \
|
|
--exchange=amq.direct \
|
|
--exchange-type=direct \
|
|
--routing-key=user.* \
|
|
--tag=service-users-consumer
|
|
```
|
|
|
|
### 4. Handling Received Events
|
|
|
|
When a message is received from RabbitMQ, it is automatically deserialized and dispatched as a Laravel event. You can listen to these events normally:
|
|
|
|
```php
|
|
namespace App\Listeners;
|
|
|
|
use App\Events\UserCreated;
|
|
use Illuminate\Support\Log;
|
|
|
|
class SendWelcomeEmail
|
|
{
|
|
public function handle(UserCreated $event): void
|
|
{
|
|
Log::info("User created: {$event->email}");
|
|
}
|
|
}
|
|
```
|
|
|
|
Register the listener in `app/Providers/EventServiceProvider.php`:
|
|
|
|
```php
|
|
protected $listen = [
|
|
\App\Events\UserCreated::class => [
|
|
\App\Listeners\SendWelcomeEmail::class,
|
|
],
|
|
];
|
|
```
|
|
|
|
## Architecture
|
|
|
|
### Event Publishing Flow
|
|
|
|
```
|
|
Laravel Event (Broadcast)
|
|
↓
|
|
PublishEvent (Listener)
|
|
↓
|
|
Serializer (JSON)
|
|
↓
|
|
RabbitMQ Exchange
|
|
↓
|
|
Queue
|
|
```
|
|
|
|
### Message Consumption Flow
|
|
|
|
```
|
|
RabbitMQ Queue
|
|
↓
|
|
Message Consumer
|
|
↓
|
|
Unserializer (JSON)
|
|
↓
|
|
EventMapper (Event)
|
|
↓
|
|
EventEmitter (Service)
|
|
↓
|
|
Event Listeners
|
|
```
|
|
|
|
## Microservices Architecture Example
|
|
|
|
### Service 1: Publishes event
|
|
|
|
```php
|
|
namespace App\Events;
|
|
|
|
use Diffhead\PHP\LaravelRabbitMQ\Event\Broadcast;
|
|
use Diffhead\PHP\LaravelRabbitMQ\Trait\BroadcastEvent;
|
|
|
|
class UserCreated implements Broadcast
|
|
{
|
|
use BroadcastEvent;
|
|
|
|
public function __construct(
|
|
public int $id,
|
|
public string $email,
|
|
public string $name,
|
|
) {}
|
|
|
|
public function getRoutingKey(): string
|
|
{
|
|
return 'user.created';
|
|
}
|
|
|
|
public function jsonSerialize(): array
|
|
{
|
|
return [
|
|
'id' => $this->id,
|
|
'email' => $this->email,
|
|
'name' => $this->name,
|
|
];
|
|
}
|
|
}
|
|
```
|
|
|
|
```php
|
|
use App\Events\UserCreated;
|
|
|
|
/**
|
|
* Controller method
|
|
*/
|
|
public function store(Request $request)
|
|
{
|
|
$user = User::create($request->validated());
|
|
UserCreated::dispatch($user->id, $user->email, $user->name);
|
|
|
|
return response()->json($user, 201);
|
|
}
|
|
```
|
|
|
|
### Service 2: Receives event
|
|
|
|
Map event using configuration:
|
|
|
|
```php
|
|
'map' => [
|
|
\App\Events\UserCreated::class => [
|
|
'queues' => ['service2.users'],
|
|
'routing_keys' => ['user.created']
|
|
]
|
|
]
|
|
```
|
|
|
|
Then implement and register event listener:
|
|
|
|
|
|
```php
|
|
namespace App\Listeners;
|
|
|
|
use App\Events\UserCreated;
|
|
|
|
class SyncUserToCalendar
|
|
{
|
|
public function handle(UserCreated $event): void
|
|
{
|
|
CalendarUser::create([
|
|
'external_id' => $event->id,
|
|
'email' => $event->email,
|
|
'name' => $event->name,
|
|
]);
|
|
}
|
|
}
|
|
```
|
|
|
|
Start consumer
|
|
|
|
```bash
|
|
php artisan rabbitmq:consume --queue=service2.users --routing-key=user.* --exchange=amq.topic --exchange-type=topic --exchange-is-default
|
|
```
|
|
|
|
## Serialization
|
|
|
|
The package uses JSON for serialization/deserialization of data via `Serializer` and `Unserializer` interfaces.
|
|
|
|
### Custom Serialization
|
|
|
|
You can use your own serialization classes by implementing
|
|
interfaces and overriding following configuration entities:
|
|
|
|
```php
|
|
'message' => [
|
|
'serializer' => \App\Services\CustomSerializer::class,
|
|
'unserializer' => \App\Services\CustomUnserializer::class,
|
|
],
|
|
```
|
|
|
|
## Mapping
|
|
|
|
The package maps rabbitmq message to application events
|
|
|
|
### Custom mapping
|
|
|
|
You can use your own mapping logic by implementing EventMapper
|
|
interface and overriding the following configuration entity:
|
|
|
|
```php
|
|
'event' => [
|
|
'mapper' => \App\Services\CustomEventMapper::class,
|
|
]
|
|
```
|