Laravel事件源Eventstore桥接层

v1.1.0 2020-05-20 14:35 UTC

This package is auto-updated.

Last update: 2024-09-21 20:28:17 UTC


README

或者德语中的“read”(读取),这在某种程度上适用于事件源。它几乎是一个好名字。

此包用Laravel事件源EventStore替换了事件和快照存储模型。EventStore在数据库之上具有一些优势,因为它是为事件源专门构建的。

该包还包括一个订阅命令,以便您可以监听系统其他服务中起源的事件。

安装

首先,让我们将包和Laravel事件源引入我们的Laravel应用程序。

composer require digitalrisks/lese

然后发布Laravel事件源和Lese配置文件。

php artisan vendor:publish --provider="Spatie\EventSourcing\EventSourcingServiceProvider" --tag="config"
php artisan vendor:publish --provider="DigitalRisks\Lese\LeseServiceProvider" --tag="config"

然后进入config/event-sourcing.php以将EventStore配置为我们的事件和快照存储仓库。

    /*
     * This class is responsible for storing events. To add extra behaviour you
     * can change this to a class of your own. The only restriction is that
     * it should implement \Spatie\EventSourcing\StoredEventRepository.
     */
    'stored_event_repository' => \DigitalRisks\Lese\EventStoreStoredEventRepository::class,

    /*
     * This class is responsible for storing snapshots. To add extra behaviour you
     * can change this to a class of your own. The only restriction is that
     * it should implement \Spatie\EventSourcing\StoredEventRepository.
     */
    'snapshot_repository' => \DigitalRisks\Lese\EventStoreSnapshotRepository::class,

配置

这是在config/lese.php中发布的配置文件的默认内容

<?php

return [
    /**
     * The EventStore connection to use when subscribing to events from external
     * services. Works with TCP or TLS connections.
     */
    'tcp_url' => env('EVENTSTORE_TCP_URL', 'tcp://admin:changeit@localhost:1113'),

    /**
     * The EventStore connection to use when publishing and reconstituting
     * aggregates. Supports HTTP or HTTPS.
     */
    'http_url' => env('EVENTSTORE_HTTP_URL', 'http://admin:changeit@localhost:2113'),

    /**
     * Listen to these streams when running `event-sourcing:subscribe`. Uses
     * a comma delimetered list from the environment as default.
     */
    'subscription_streams' => array_filter(explode(',', env('EVENTSTORE_SUBSCRIPTION_STREAMS'))),

    /**
     * Used as the group when connecting to an EventStore persisten subscription.
     */
    'group' => env('EVENTSTORE_SUBSCRIPTION_GROUP', env('APP_NAME', 'laravel')),

    /**
     * By default Aggregate classes are mapped to a category name based on their
     * class name. Example App\Aggregates\AccountAggregate would be published
     * to an account-uuid stream. This allows you to implicitly map classes
     * to categories so that it could be published to account_v2-uuid.
     */
    'aggregate_category_map' => [],

    /**
     * If not using aggregates, events need to mapped to streams to be
     * published. An example would be the AccoutCreated event
     * could be published on to the accounts stream.
     */
    'event_stream_map' => [],

    /**
     * If the event is not mapped to a stream,
     * publish to this stream by default.
     */
    'default_stream' => env('EVENTSTORE_DEFAULT_STREAM', 'events'),

    /**
     * The stream to listen to when replaying all events. Instead of using
     * $all, it is recommended to setup a project which emits events
     * from various streams into a stream specific for your app.
     */
    'all' => env('EVENTSTORE_ALL', '$all'),

    /**
     * Number of events to read in a single API
     * call when reconstituting events.
     */
    'read_size' => env('EVENTSTORE_READ_SIZE', 4096),

    /**
     * Number of events to read in a single TCP
     * message when replaying all events.
     */
    'batch_size' => env('EVENTSTORE_BATCH_SIZE', 4096),

    /**
     * This class contains a few callbacks to govern the bridge between EventStore and the
     * Laravel Event Sourcing package. You can customise the class to include your
     * own business logic. It should extend DigitalRisks\Lese\Lese
     */
    'lese_class' => env('EVENTSTORE_LESE_CLASS', DigitalRisks\Lese\Lese::class),
];

入门

我建议通过阅读https://docs.spatie.be/laravel-event-sourcing/v3/introduction/上的优秀指南来熟悉事件源。

下一步是获取EventStore的本地版本(您不需要数据库)。每个平台的说明都可以在https://eventstore.com/docs/getting-started/index.html找到

现在让我们创建一个简单的事件

<?php

namespace App\Events;

use Spatie\EventSourcing\ShouldBeStored;

class MoneyAdded implements ShouldBeStored
{
    /** @var string */
    public $accountUuid;

    /** @var int */
    public $amount;

    public function __construct(string $accountUuid, int $amount)
    {
        $this->accountUuid = $accountUuid;

        $this->amount = $amount;
    }
}

并发送它

<?php

event(new MoneyAdded('21410-81231', 100))

现在让我们创建一个简单的投影,将账户信息放入数据库。

<?php

namespace App\Projectors;

use App\Account;
use App\Events\AccountCreated;
use App\Events\AccountDeleted;
use App\Events\MoneyAdded;
use App\Events\MoneySubtracted;
use Spatie\EventSourcing\Projectors\Projector;
use Spatie\EventSourcing\Projectors\ProjectsEvents;

class AccountsProjector implements Projector
{
    use ProjectsEvents;

    public function onMoneyAdded(MoneyAdded $event)
    {
        $account = Account::uuid($event->accountUuid);

        $account->balance += $event->amount;

        $account->save();
    }
}

并将事件发送给FBI处理大额交易

<?php

namespace App\Reactors;

use App\Account;
use App\Events\MoneyAdded;
use App\Mail\BigAmountAddedMail;
use Illuminate\Support\Facades\Mail;
use Spatie\EventSourcing\EventHandlers\EventHandler;
use Spatie\EventSourcing\EventHandlers\HandlesEvents;

class BigAmountAddedReactor implements EventHandler
{
    use HandlesEvents;

    public function onMoneyAdded(MoneyAdded $event)
    {
        if ($event->amount < 5000) {
            return;
        }

        $account = Account::uuid($event->accountUuid);

        Mail::to('director@fbi.gov')->send(new BigAmountAddedMail($account, $event->amount));
    }
}

如果以后业务想要在模型上添加number_of_deposits属性,我们更新投影器

<?php

namespace App\Projectors;

use App\Account;
use App\Events\AccountCreated;
use App\Events\AccountDeleted;
use App\Events\MoneyAdded;
use App\Events\MoneySubtracted;
use Spatie\EventSourcing\Projectors\Projector;
use Spatie\EventSourcing\Projectors\ProjectsEvents;

class AccountsProjector implements Projector
{
    use ProjectsEvents;

    public function onMoneyAdded(MoneyAdded $event)
    {
        $account = Account::uuid($event->accountUuid);

        $account->balance += $event->amount;
      	$account->number_of_deposits += 1;

        $account->save();
    }
}

并重新运行事件

php artisan event-sourcing:replay App\\Projectors\\AccountsProjector

通过遵循https://docs.spatie.be/laravel-event-sourcing/v3/introduction/上的指南来了解更多关于如何使用事件源的信息

聚合

如果您不使用聚合,可以跳过此部分。

为了使EventStore仓库能够获取与聚合相关的事件和/或快照,它需要了解聚合。为此,我们只需简单地覆盖以下两个方法以启动仓库并传入聚合。

protected function getStoredEventRepository(): StoredEventRepository
{
    return resolve(EventStoreStoredEventRepository::class, ['aggregate' => $this]);
}

protected function getSnapshotRepository(): SnapshotRepository
{
    return resolve(EventStoreSnapshotRepository::class, ['aggregate' => $this]);
}

订阅流

该包还包括一个长时间运行的过程,类似于Pub / Sub,通过php artisan redis:subscribe,您可以监听来自流的事件。

假设这是accounts-service,但我们想监听来自quotes-service的事件。当报价被转换时,我们希望为其创建一个账户。

小心:如果您监听您发布的事件,投影器和反应器将在您的应用程序中处理它们一次,并在它们再次流回时再次处理。建议您仅订阅您不发布的事件流。

config/lese.php中,我们将添加报价转换事件的流

/**
 * Listen to these streams when running `event-sourcing:subscribe`. Uses
 * a comma delimetered list from the environment as default.
 */
'subscription_streams' => ['$et-Events\Quotes\QuoteConverted'],

然后我们可以运行以下命令在EventStore上创建持久订阅

php artisan event-sourcing:reset

小心:在重置持久订阅时,它将从第一个事件重新开始。如果您有reactors,您应该在eventstore管理员中设置从何处开始的值,以您希望开始的事件编号。

最后开始订阅过程

php artisan event-sourcing:subscribe

事件元数据

元数据可以帮助跟踪您系统中的事件。您可以在事件中包含以下任何属性来自动附加元数据

  • 添加Heroku元数据
  • 添加Laravel元数据
  • 添加用户元数据

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

<?php

namespace App\Events;

use DigitalRisks\Lese\MetaData\HasMetaData;
use DigitalRisks\Lese\MetaData\CollectsMetaData;

use DigitalRisks\Lese\MetaData\AddsHerokuMetadata;
use DigitalRisks\Lese\MetaData\AddsLaravelMetadata;
use DigitalRisks\Lese\MetaData\AddsUserMetaData;

use Spatie\EventSourcing\ShouldBeStored;

class MoneyAdded implements ShouldBeStored, HasMetaData
{
    use CollectsMetaData, AddsUserMetaData, AddsHerokuMetadata, AddsLaravelMetadata;

    /** @var string */
    public $accountUuid;

    /** @var int */
    public $amount;

    public function __construct(string $accountUuid, int $amount)
    {
        $this->accountUuid = $accountUuid;

        $this->amount = $amount;
    }
  
    /** @metadata */
    public function collectIpMetadata()
    {
        return [
            'ip' => $_SERVER['REMOTE_ADDR'],
        ];
    }
}

更新日志

请参阅更新日志了解最近更改了哪些信息。

贡献

请参阅贡献指南了解详细信息。

安全

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

致谢

许可证

MIT许可证(MIT)。请参阅许可证文件以获取更多信息。