digitalrisks/laravel-eventstore

关于将Greg Young的Event Store集成到Laravel的所有内容

v8.4.1 2022-02-18 10:50 UTC

README

此包将Greg Young的eventstore集成到Laravel的事件系统中。通过简单地在你的事件上实现ShouldBeStored接口,它们将被发送到eventstore。同样地,你也可以设置监听器来响应从eventstore接收到的事件。

示例实现: https://github.com/digitalrisks/laravel-eventstore-example

安装

您可以通过composer安装此包。

composer require digitalrisks/laravel-eventstore

添加包的基本服务提供者。

<?php

namespace App\Providers;

use Illuminate\Support\Str;
use DigitalRisks\LaravelEventStore\EventStore;
use DigitalRisks\LaravelEventStore\ServiceProvider as EventStoreApplicationServiceProvider;

class EventStoreServiceProvider extends EventStoreApplicationServiceProvider
{
    /**
     * Bootstrap the application services.
     */
    public function boot()
    {
        parent::boot();
    }

    /**
     * Set the eventToClass method.
     *
     * @return void
     */
    public function eventClasses()
    {
        // This will set your events to be the following '\\App\Events\\' . $event->getType();.
        EventStore::eventToClass();

        // You can customise this by doing the following.
        EventStore::eventToClass(function ($event) {
            return 'App\Events\\' . Str::studly($event->getType());
        });
    }

    /**
     * Handle logging when event is triggered.
     *
     * @return void
     */
    public function logger()
    {
        // This will setup the logger for when an event happens.
        EventStore::logger();

        // You can customise this by doing the following.
        EventStore::logger(function ($event, $type) {
            Log::info($event->getType());
        });
    }

    /**
     * Register the application services.
     */
    public function register()
    {
        parent::register();
    }
}

在你的config/app.php文件中,将以下内容添加到providers数组中。

    /*
    |--------------------------------------------------------------------------
    | Autoloaded Service Providers
    |--------------------------------------------------------------------------
    |
    | The service providers listed here will be automatically loaded on the
    | request to your application. Feel free to add your own services to
    | this array to grant expanded functionality to your applications.
    |
    */

    'providers' => [
        ...
        App\Providers\EventStoreServiceProvider::class,
    ],

示例事件

use DigitalRisks\LaravelEventStore\Contracts\CouldBeReceived;
use DigitalRisks\LaravelEventStore\Contracts\ShouldBeStored;
use DigitalRisks\LaravelEventStore\Traits\ReceivedFromEventStore;
use DigitalRisks\LaravelEventStore\Traits\SendsToEventStore;

class QuoteStarted implements ShouldBeStored, CouldBeReceived
{
    use SendsToEventStore, ReceivedFromEventStore;

    public $email;

    /**
     * Create a new event instance.
     *
     * @return void
     */
    public function __construct($email = null)
    {
        $this->email = $email;
    }
}

用法 - 发送事件

此包将自动发送实现了ShouldBeStored接口的Laravel事件。

interface ShouldBeStored
{
    public function getEventStream(): string;

    public function getEventType(): string;

    public function getEventId(): string;

    public function getData(): array;

    public function getMetadata(): array;
}

为了帮助实现接口,此包提供了一个SendsToEventStore特质,它可以以基本方式满足接口的要求

  • 事件类型:事件的类名
  • 事件ID:将生成一个UUID v4
  • 数据:所有事件的公共属性都将自动序列化
  • 元数据:将从所有标记有@metadata的方法收集数据并进行序列化
use DigitalRisks\LaravelEventStore\Contracts\CouldBeReceived;
use DigitalRisks\LaravelEventStore\Contracts\ShouldBeStored;
use DigitalRisks\LaravelEventStore\Traits\SendsToEventStore;

class AccountCreated implements ShouldBeStored, CouldBeReceived
{
    use SendsToEventStore;
    
    public function getEventStream(): string
    {
        return 'accounts';
    }
}

然后以正常的Laravel方式引发事件

event(new AccountCreated('foo@bar.com'));

元数据

元数据可以帮助追踪系统中的事件。您可以在事件上包含以下任何特质来自动附加元数据

  • AddsHerokuMetadata
  • AddsLaravelMetadata
  • AddsUserMetaData

或者您也可以定义自己的方法来收集元数据。任何带有@metadata注解的方法都将被调用

class AccountCreated implements ShouldBeStored
{
    use DigitalRisks\LaravelEventStore\Traits\AddsLaravelMetadata;
    
    /** @metadata */
    public function collectIpMetadata()
    {
        return [
            'ip' => $_SERVER['REMOTE_ADDR'],
        ];
    }
}

测试

如果您想测试事件是否正确触发,可以使用Laravel的Event::mock方法,或者此包包含与eventstore交互的助手函数,以确认它们是否已被正确存储。

class AccountCreatedTest extends TestCase
{
    use DigitalRisks\LaravelEventStore\Tests\Traits\InteractsWithEventStore;

    public function test_it_creates_an_event_when_an_account_is_created()
    {
        // Act.
        $this->json('POST', '/api/accounts', ['email' => 'foo@bar.com']);

        // Assert.
        $this->assertEventStoreEventRaised('AccountCreated', 'accounts', ['email' => 'foo@bar.com']);
    }
}

用法 - 接收事件

您必须首先运行工作进程,该工作进程将监听事件。

没有任何选项是必需的。默认情况下,它将使用10秒超时和每次1个并行事件的持久订阅来运行。

$ php artisan eventstore:worker {--parallel= : How many events to run in parallel.} {--timeout= : How long the event should time out for.}

$ php artisan eventstore:worker --parallel=10 --timeout=5

收到事件后,它将被映射到Laravel事件,并且可以通过getEventRecord()访问原始的EventRecord

您可以使用正常的Laravel方式对这些事件做出反应。

class EventServiceProvider extends ServiceProvider
{
    protected $listen = [
        AccountCreated::class => [SendAccountCreatedEmail::class],
    ];
}
class SendAccountCreatedEmail
{
    public function handle(AccountCreated $event)
    {
        Mail::to($event->email)->send('Here is your account');
    }
}

如果您正在监听与您触发事件相同的流,您的事件将会被触发两次 - 一次由Laravel触发,一次从event store接收。您可以选择在$event->getEventRecord()为false时同步反应,如果$event->getEventRecord()返回eventstore记录,则异步反应。

class SendAccountCreatedEmail
{
    public function handle(AccountCreated $event)
    {
        // Side effect, let's only send an email when we've triggered this event and not when replaying events
        if (! $event->getEventRecord()) return;

        Mail::to($event->email)->send('Here is your account');
    }
}

class SaveAccountToDatabase
{
    public function handle(AccountCreated $event)
    {
        // State change, let's ensure we update our database with this event.
        if ($event->getEventRecord()) return;

        Account::create(['email' => $event->email]);
    }
}

此外,如果您想测试事件是否已创建以及应用程序如何对这些事件做出反应,可以将eventstore.connection设置为sync。这将使事件监听器认为事件已从eventstore接收。

测试

如果您想测试您的监听器,此包包含几个助手方法来模拟从工作进程接收事件。

class QuoteStartedTest extends TestCase
{
    use \DigitalRisks\LaravelEventStore\Tests\MakesEventRecords;

    public function test_it_sends_an_email_when_an_account_is_created()
    {
        // Arrange.
        $event = $this->makeEventRecord('AccountCreated', ['email' => 'foo@bar.com');

        // Act.
        event($event->getType(), $event);

        // Assert.
        Mail::assertSentTo('foo@bar.com');
    }
}

此外,您可以将eventstore.connection设置为sync,这将欺骗您的监听器

用法 - 重放事件

您可以使用重放命令来重放事件

php artisan eventstore:replay <stream> <event>

<event> 可以是一个单独的事件编号,也可以是一个范围,例如 390-396

配置

默认设置在 config/eventstore.php 中。将此文件复制到您的配置目录中,以修改这些值

php artisan vendor:publish --provider="DigitalRisks\LaravelEventStore\ServiceProvider"
return [
    'tcp_url' => 'tls://admin:changeit@localhost:1113',
    'http_url' => 'http://admin:changeit@localhost:2113',
    'group' => 'account-email-subscription',
    'volatile_streams' => ['quotes', 'accounts'],
    'subscription_streams' => ['quotes', 'accounts'],
];

测试

composer test

变更日志

有关最近变更的详细信息,请参阅 变更日志

贡献

有关详细信息,请参阅 贡献指南

安全性

如果您发现任何与安全相关的问题,请通过电子邮件 pawel.trauth@digitalrisks.co.uk 联系,而不是使用问题跟踪器。

鸣谢

许可证

MIT 许可证(MIT)。有关更多信息,请参阅 许可证文件