All Projects → nuwber → Rabbitevents

nuwber / Rabbitevents

Licence: mit
Nuwber's events provide a simple observer implementation, allowing you to listen for various events that occur in your current and another application. For example, if you need to react to some event published from another API.

Projects that are alternatives of or similar to Rabbitevents

Remit
RabbitMQ-backed microservices supporting RPC, pubsub, automatic service discovery and scaling with no code changes.
Stars: ✭ 24 (-71.43%)
Mutual labels:  microservice, events, rabbitmq
eshopzero
.Net Microservice Application
Stars: ✭ 27 (-67.86%)
Mutual labels:  microservice, rabbitmq, eventbus
Laravel Queue Rabbitmq
RabbitMQ driver for Laravel Queue. Supports Laravel Horizon.
Stars: ✭ 1,175 (+1298.81%)
Mutual labels:  laravel, rabbitmq, queue
horizon-exporter
Export Laravel Horizon metrics using this Prometheus exporter.
Stars: ✭ 17 (-79.76%)
Mutual labels:  events, queue, broadcast
Cap
Distributed transaction solution in micro-service base on eventually consistency, also an eventbus with Outbox pattern
Stars: ✭ 5,208 (+6100%)
Mutual labels:  microservice, eventbus, rabbitmq
Goodskill
🐂基于springcloud +dubbo构建的模拟秒杀项目,模块化设计,集成了分库分表、elasticsearch🔍、gateway、mybatis-plus、spring-session等常用开源组件
Stars: ✭ 786 (+835.71%)
Mutual labels:  microservice, rabbitmq
Laravel Google Calendar
Manage events on a Google Calendar
Stars: ✭ 787 (+836.9%)
Mutual labels:  events, laravel
Bigq
Messaging platform in C# for TCP and Websockets, with or without SSL
Stars: ✭ 18 (-78.57%)
Mutual labels:  broadcast, queue
Pro
ECStore Pro - Laravel 微信网店微服务框架
Stars: ✭ 14 (-83.33%)
Mutual labels:  microservice, laravel
Laravel Couchbase
Couchbase providers for Laravel
Stars: ✭ 31 (-63.1%)
Mutual labels:  laravel, queue
Yii2 Queue
Yii2 Queue Extension. Supports DB, Redis, RabbitMQ, Beanstalk and Gearman
Stars: ✭ 977 (+1063.1%)
Mutual labels:  rabbitmq, queue
Spring Cloud Netflix Example
spring-cloud-netflix-example is an example for microservices system
Stars: ✭ 760 (+804.76%)
Mutual labels:  microservice, rabbitmq
Laravel Event Projector
Event sourcing for Artisans 📽
Stars: ✭ 650 (+673.81%)
Mutual labels:  events, laravel
Laravel Event Broadcast
Laravel event broadcasting with Node.js, Redis & Socket.io
Stars: ✭ 5 (-94.05%)
Mutual labels:  events, laravel
Node Celery
Celery client for Node.js
Stars: ✭ 648 (+671.43%)
Mutual labels:  rabbitmq, queue
Laravel Failed Job Monitor
Get notified when a queued job fails
Stars: ✭ 582 (+592.86%)
Mutual labels:  laravel, queue
Kbframe
一款基于Laravel框架开发的现代化二次开发框架,是高性能,高效率,高质量的企业级开发框架,具有驱动领域,敏捷开发,轻易上手,高内聚低耦合,开箱即用等特点。
Stars: ✭ 47 (-44.05%)
Mutual labels:  laravel, rabbitmq
Yii Queue
Queue extension for Yii 3.0
Stars: ✭ 38 (-54.76%)
Mutual labels:  rabbitmq, queue
Laravel Elasticbeanstalk Queue Worker
Stars: ✭ 48 (-42.86%)
Mutual labels:  laravel, queue
Machinery
Machinery is an asynchronous task queue/job queue based on distributed message passing.
Stars: ✭ 5,821 (+6829.76%)
Mutual labels:  rabbitmq, queue

Events publishing for Laravel by using RabbitMQ

Tests Status Total Downloads Latest Version License

Nuwber's events provides a simple observer implementation, allowing you to listen for various events that occur in your current and another applications. For example if you need to react to some event published from another API.

Do not confuse this package with Laravel's broadcast. This package was made to communicate a backend to backend.

It's actually a compilation of Laravel's events and queues with some improvements, such as middleware.

Listener classes are typically stored in the app/Listeners folder. You may use Laravel's artisan command to generate them as it described in the official documentation.

All RabbitMQ calls are done by using Laravel queue package as an example. So for better understanding read their documentation first.

Table of contents

  1. Installation
  2. Events
  3. Listeners
  4. Console commands
  5. Logging
  6. Testing
  7. Examples
  8. Non-standard use

Installation

You may use Composer to install RabbitEvents into your Laravel project:

$ composer require nuwber/rabbitevents

After installing RabbitEvents, publish its config and a service provider using the rabbitevents:install Artisan command:

$ php artisan rabbitevents:install

Configuration

After publishing assets, its primary configuration file will be located at config/rabbitevents.php. This configuration file allows you to configure your connection and logging options.

It's very similar to queue connection but now you'll never be confused if you have another connection to RabbitMQ.

<?php
return [
    'default' => env('RABBITEVENTS_CONNECTION', 'rabbitmq'),
    'connections' => [
        'rabbitmq' => [
            'driver' => 'rabbitmq',
            'exchange' => env('RABBITEVENTS_EXCHANGE', 'events'),
            'host' => env('RABBITEVENTS_HOST', 'localhost'),
            'port' => env('RABBITEVENTS_PORT', 5672),
            'user' => env('RABBITEVENTS_USER', 'guest'),
            'pass' => env('RABBITEVENTS_PASSWORD', 'guest'),
            'vhost' => env('RABBITEVENTS_VHOST', 'events'),
            'logging' => [
                'enabled' => env('RABBITEVENTS_LOG_ENABLED', false),
                'level' => env('RABBITEVENTS_LOG_LEVEL', 'info'),
            ],
        ],
    ],
];

Events

This is the example how to publish your event and payload:

<?php
$model = new SomeModel(['name' => 'Jonh Doe', 'email' => '[email protected]']);
$someArray = ['key' => 'item'];
$someString = 'Hello!';

// Example 1. Old way to publish your data. Will be deprecated it next versions.
// Remember: You MUST pass array of arguments
fire('something.happened', [$model->toArray(), $someArray, $someString]);

// Example 2. Method `publish` from `Publishable` trait
SomeEvent::publish($model, $someArray, $someString);

$someEvent = new SomeEvent($model, $someArray, $someString);

// Example 3. Use helper `publish`
publish($someEvent);

// Example 4. You could use helper `publish` as you used to use helper `fire`
publish('something.happened', [$model->toArray(), $someArray, $someString]);
publish($someEvent->publishEventName(), $someEvent->toPublish());

Defining Events

If you want to make your event class publishable you should implement interface ShouldPublish. Example of such class you could see here. If you'll try to publish an event without implementation, the exception InvalidArgumentException('Event must be a string or implement "ShouldPublish" interface') will be thrown.

If you want to add method publish to an event class (Example 2) you could use the trait Publishable.

There are helper functions publish and fire (will be deprecated in next versions). Examples 1, 3 and 4 illustrates how to use them.

Retrying Failed Events

The rabbitevents:listen command sets number of tries to handle a Job to 1 by default. This means that there will be 2 attempts (first attempt and 1 retry) to handle your event with delay of sleep option (default is 5 seconds). --tries=0 means that Rabbitevents will attempt to handle an event forever. If for some reason event handling shouldn't be retried, throw \Nuwber\Events\Exception\FailedException from a Listener. It will mark an event Job as failed without new attempts to handle.

More examples here

Listeners

Register a Listener

The listen property of RabbitEventsServiceProvider contains an array of all events (keys) and their listeners (values). Of course, you may add as many events to this array as your application requires.

<?php
/**
 * The event listener mappings for the application.
 *
 * @var array
 */
protected $listen = [
    'item.created' => [
        'App\Listeners\SendItemCreatedNotification',
        'App\Listeners\ChangeUserRole',
    ],
];

Wildcard Event Listeners

You may even register listeners using the * as a wildcard parameter, allowing you to catch multiple events on the same listener. Wildcard listeners receive the event name as their first argument, and the entire event data array as their second argument:

<?php
/**
 * The event listener mappings for the application.
 *
 * @var array
 */
protected $listen = [
    'item.*' => [
        'App\Listeners\ItemLogger',
    ],
];

Defining Listeners

Event listeners receive the event data (usually this is an array) in their handle method. Within the handle method, you may perform any actions necessary to respond to the event:

<?php

namespace App\Listeners;

class ItemLogger
{

    /**
     * Handle the event.
     *
     * @param  array $payload
     * @return void
     */
    public function handle(array $payload)
    {
        log(...);
    }
}

Listeners for wildcard events

There's difference for handle method of listeners for wildcard events. It receives fired event name as a first argument and payload as the second:

<?php

namespace App\Listeners;

class ItemLogger
{

    /**
     * Handle the event.
     *
     * @param  string $event
     * @param  array $payload
     * @return void
     */
    public function handle(string $event, array $payload)
    {
        if ($event === 'item.created') {
            // do something special
        }

       log(...);
    }
}

Middleware

Listener middleware allow you to wrap custom logic around the execution of a listener job, reducing boilerplate in the jobs themselves. For example, we have an event 'charge.succeeded' which can be handled in several APIs but only if this payment is for its entity.

<?php

/**
 * Hadle payment but only if a type is 'mytype'
 *
 * @param array $payload
 * @return void
 */ 
public function handle($payload) 
{
    if (\Arr::get($payload, 'entity.type') !== 'mytype') {
        return;
    }   
    
    Entity::find(\Arr::get($payload, 'entity.id'))->activate();
}

It is ok if you have only one listener. What if there're many listeners, that must implement the same check on the start of each handle method? Code will become a bit noisy.

Instead of writing same check at the start of each listener, we could define listener middleware that handles an entity type.

<?php

namespace App\Listeners\RabbitEvents\Middleware;

class FilterEntities
{
    /** 
     * @param string $event Event Name. Passing only fpr wildcard events
     * @param array $payload
     */
    public function handle([$event,] $payload)
    {
        return !\Arr::get($payload, 'entity.type') == 'mytype';
    }
}

It doesn't work as route middleware. I still didn't find a beautiful way how to pass only $payload for a simple event and $event plus $payload for wildcard ones.

If we need to stop propagation, just return false.

After creating listener middleware, they may be attached to a listener by returning them from the listener's middleware method or as an array item from $middleware attribute.

<?php

use App\Listeners\RabbitEvents\Middleware\FilterEntities;

class PaymentListener
{
    public array $middleware = [
        FilterEntities::class,
        `App\Listeners\RabbitEvents\Middleware\[email protected]`,
    ];      

    /** 
     * @param string $event Event Name. Passing only for wildcard events
     * @param array $payload
     */
    public function middleware([$event, ]$payload)
    {
        return !\Arr::get($payload, 'entity.type') == 'mytype';  
    }
}

This is just illustration how you could pass middleware to listener. You could choose the way you prefer.

Stopping The Propagation Of An Event

Sometimes, you may wish to stop the propagation of an event to other listeners. You may do so by returning false from your listener's handle method as it is in Laravel's listeners.

Console commands

Command rabbitevents:install

If you don't want manually create config file and register a service provider, you may run the command rabbitevents:install which will automatically do all this stuff.

$ php artisan rabbitevents:install 

Command rabbitevents:listen

There is the command which is registers events in RabbitMQ:

$ php artisan rabbitevents:listen event.name --memory=512 --timeout=60 --tries=3 --sleep=5

After this command start event will be registered in RabbitMQ as a separate queue which has bind to an event.

To detach command from console you can run this way:

$ php artisan rabbitevents:listen event.name > /dev/null &

In this case you need to remember that you have organize some system such as Supervisor or pm2 which will controll your processes.

If your listener crashes then the managers will rerun your listener and all messages that were sent to queue will be handled in same order as they were sent. There're known problem: as queues are separated and you have messages that affects the same entity there's no guaranty that all actions will be done in expected order. To avoid such problems you can send message time as a part of payload and handle it internally in your listeners.

Command rabbitevents:list

To get list of all registered events there's the command:

$ php artisan rabbitevents:list

Command rabbitevents:make:observer

Sometimes you may with to publish an event to each change of a model. Observers classes have method names which reflect the Eloquent events you wish to listen for. Each of these methods receives the model as their only argument. The difference from Laravel's command is that rabbitevents:make:observer creates an observer class with stubbed fire function call in each method.

$ php artisan rabbitevents:make:observer UserObserver --model=User

This command will place the new observer in your App/Observers directory. If this directory does not exist, Artisan will create it for you. Your fresh observer will look like the following:

<?php

namespace App\Observers;

use App\User;

class UserObserver
{
    /**
     * Handle the user "created" event.
     *
     * @param  \App\User  $user
     * @return void
     */
    public function created(User $user)
    {
        publish('User.created', $user->toArray());
    }

    /**
     * Handle the user "updated" event.
     *
     * @param  \App\User  $user
     * @return void
     */
    public function updated(User $user)
    {
        publish('User.updated', $user->toArray());
    }

    /**
     * Handle the user "deleted" event.
     *
     * @param  \App\User  $user
     * @return void
     */
    public function deleted(User $user)
    {
        publish('User.deleted', $user->toArray());
    }

    /**
     * Handle the user "restored" event.
     *
     * @param  \App\User  $user
     * @return void
     */
    public function restored(User $user)
    {
        publish('User.restored', $user->toArray());
    }

    /**
     * Handle the user "force deleted" event.
     *
     * @param  \App\User  $user
     * @return void
     */
    public function forceDeleted(User $user)
    {
        publish('User.forceDeleted', $user->toArray());
    }
}

To register an observer, use the observe method on the model you wish to observe. You may register observers in the boot method of one of your service providers. In this example, we'll register the observer in the AppServiceProvider:

<?php

namespace App\Providers;

use App\User;
use App\Observers\UserObserver;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        User::observe(UserObserver::class);
    }

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        //
    }
}

Logging

The package provides 2 ways to see what happens on your listener. By default it writes processing, processed and failed messages to php output. Message includes service, event and listener name. If you want to turn this feature off, just run listener with --quiet option.

The package also supports your application logger. To use it set config value rabbitevents.connection.rabbitmq.logging.enabled to true and choose log level.

Testing

We always write tests. Tests in our applications contains many mocks and fakes to test how events published. We've made this process a bit easier for Event classes that implements ShouldPublish and uses Publishable trait.

<?php
use \App\Listeners\Listener;

Listener::fake();

$payload = [
    "key1" => 'value1',
    "key2" => 'value2',
];

Listener::publish($payload);

Listener::assertPublished('something.happened', $payload);

AnotherListener::assertNotPublished();

If assertion not passes Mockery\Exception\InvalidCountException will bw thrown. Don't forget to call \Mockery::close() in tearDown or similar methods of your tests.

Non-standard use

If you're using only one of parts of RabbitEvents, you should know a few things:

  1. You remember, we're using RabbitMQ as the transport layer. In the RabbitMQ Documentation you could find examples how to publish your messages using a routing key. This is an event name like something.happened from examples above.

  2. RabbitEvents expects that a message body is a JSON encoded array. Every element of an array will be passed to a Listener as a separate variable. Example:

[
  {
    "key": "value"  
  },
  "string",
  123 
]

There'e 3 elements of an array, so 3 variables will be passed to a Listener (array, string and integer). If an associative array is being passed, the Dispatcher wraps this array by itself.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].