lingoda/domain-events

Lingoda Domain Events Bundle

安装次数: 7,094

依赖者: 0

建议者: 0

安全性: 0

星标: 7

关注者: 21

分支: 3

开放问题: 1

类型:symfony-bundle

2.0.3 2024-09-04 18:06 UTC

README

安装

composer req lingoda/domain-events

组件配置

# config/packages/domain_events.yaml

lingoda_domain_events:
    message_bus_name: 'event.bus'

    // default is false, you can turn on event publishing on Kernel, Console and WorkerMessageHandledEvent events, usefull in test environment
    enable_event_publisher: true

用法

重要提示: 在 doctrine 生命周期钩子中切勿记录领域事件!

示例:简单的 User 实体触发领域事件。

示例领域事件

use Lingoda\DomainEventsBundle\Domain\Model\DomainEvent;
use Lingoda\DomainEventsBundle\Domain\Model\Traits\DomainEventTrait;

/**
 * Sample domain event
 */
class UserCreatedEvent implements DomainEvent
{
    use DomainEventTrait;

    private string $username;

    public function __construct(string $entityId, string $username)
    {
        $this->username = $username;
        $this->init($entityId);
    }

    public function getUsername(): string
    {
        return $this->username;
    }
}

记录事件的示例 User 实体

use Lingoda\DomainEventsBundle\Domain\Model\DomainEventAware;
use Lingoda\DomainEventsBundle\Domain\Model\Traits\EventRecorderTrait;
use Symfony\Component\Uid\Ulid;

// DomainEventAware interface is a helper that brings RecordsEvents and ContainsEvents together
class User implements DomainEventAware
{
    // helper trait for event recording
    use EventRecorderTrait;

    private Ulid $id;
    private string $username;

    public function __construct(string $username)
    {
        $this->id = new Ulid();
        $this->username = $username;

        $this->recordEvent(new UserCreatedEvent(
            $this->id->toRfc4122(),
            $username
        ));
    }

    public function getId(): Ulid
    {
        return $this->id;
    }

    public function setId(Ulid $id): void
    {
        $this->id = $id;
    }

    public function getUsername(): string
    {
        return $this->username;
    }

    public function setUsername(string $username): void
    {
        $this->username = $username;
    }
}

实际操作

// create the entity will record the domain event
$user = new User('john-doe');

$entityManager->persist($user);

/**
 * When we flush the changes PersistDomainEventsSubscriber will kick in and create a OutboxRecord entity containing
 * the domain event in it that will be stored within the same transaction together with the User entity
 */
$entityManager->flush();

// Later on the PublishDomainEventsSubscriber will publish via Messenger all unpublished Domain Event from OutboxRecord
// database on the following events KernelEvents::TERMINATE, ConsoleEvents::TERMINATE or WorkerMessageHandledEvent

使用 Messenger Worker 分发领域事件

首先配置 outbox messenger 传输

framework:
  messenger:
    transports:
      outbox:
        dsn: 'outbox://default' // the host part is the doctrine enity mananager name, this case default

    routing:
      Lingoda\DomainEventsBundle\Infra\Symfony\Messenger\OutboxMessage: outbox

然后我们可以使用以下命令消耗 Outbox 表并从其中分发领域事件

bin/console messenger:consume outbox

事件调度

我们可以安排领域事件在未来发布

// let's say we have AskForUserFeedbackEvent the following event that should be triggered 2 weeks after user registration
// and send a followup email to the user

// we could schedule this like follow

$askForUserFeedbackEvent = new AskForUserFeedbackEvent($user->getId());
$askForUserFeedbackEvent->setOccuredAt(
    new CarbonImmutable('+2 weeks')
);

$user->recordEvent($askForUserFeedbackEvent);

// this will be stored in OutboxRecord table and unpublished until the due date

在 event_store 中替换/重新安排事件

我们可以通过为领域事件实现 ReplaceableEventInterface 来替换/重新安排未发布的事件。如果你实现此接口,在 OutboxRecord 持久化器存储新的领域事件之前,它将检查是否存在来自同一实体 id 的先前存储但未发布的事件,如果存在,它将删除它们并只添加新的一个。

丰富领域事件

尽管领域事件应该是不可变的,但有时不可避免地需要添加额外的信息,但你不想在创建时分配,因为这时的服务在实体内部不可用。

你可以在 Domain Event 持久化之前发送的 subscriber/listener 中监听 PreAppendEvent。在这个时候,你可以添加额外的信息。

简单的示例是注入 actorId,它与当前与应用程序交互的用户 id 对应。

测试

安装开发依赖

# Install dev dependecies
composer install --dev

运行测试

vendor/bin/phpunit
vendor/bin/phpspec run

待办事项

  • 添加功能测试
  • 在 DSN 中使用额外的选项改进 OutboxTransportFactory
  • 添加 doctrine 映射和路由 DomainEvent 的说明
  • 修复围绕 Carbon 序列化的问题