lingoda / domain-events
Lingoda Domain Events Bundle
2.0.3
2024-09-04 18:06 UTC
Requires
- php: ^8.2
- doctrine/dbal: ^3.4
- doctrine/doctrine-bundle: ^2.8
- doctrine/orm: ^2.15
- nesbot/carbon: ^2.71 || ^3.0
- symfony/event-dispatcher: ^6.4|^7.0
- symfony/framework-bundle: ^6.4|^7.0
- symfony/lock: ^6.4|^7.0
- symfony/messenger: ^6.4|^7.0
- webmozart/assert: ^1.10
Requires (Dev)
- nyholm/symfony-bundle-test: ^3.0
- phpspec/phpspec: ^7.5
- phpstan/extension-installer: ^1.4
- phpstan/phpstan: ^1.12
- phpstan/phpstan-doctrine: ^1.5
- phpstan/phpstan-symfony: ^1.4
- phpstan/phpstan-webmozart-assert: ^1.2
- phpunit/phpunit: ^9.6
- rector/rector: ^1.2
- symfony/phpunit-bridge: ^6.4|^7.0
Suggests
- ext-amqp: *
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 序列化的问题