rekalogika / domain-event
Symfony 和 Doctrine 的领域事件实现
Requires
- doctrine/doctrine-bundle: ^2.11.3 || ^2.12
- doctrine/orm: ^2.16 || ^3.0
- doctrine/persistence: ^3.2
- psr/event-dispatcher: ^1.0
- rekalogika/domain-event-contracts: ^2.5
- symfony/config: ^6.4 || ^7.0
- symfony/dependency-injection: ^6.4 || ^7.0
- symfony/event-dispatcher: ^6.4 || ^7.0
- symfony/http-kernel: ^6.4 || ^7.0
- symfony/service-contracts: ^3.0
- symfony/var-exporter: ^6.4 || ^7.0
README
为 Symfony & Doctrine 实现领域事件模式。
完整文档可在 rekalogika.dev/domain-event 查看。
什么是领域事件?
领域事件简单地说是你通常会用 Symfony 的 EventDispatcher 使用的常规事件。区别在于,领域事件代表在你的领域中发生的事情。它有一个对领域所代表的基础业务有意义的名称。领域事件通常由你的实体分发,而不是从控制器或其他服务分发。
为什么使用领域事件?
领域事件代表已经发生的业务事件。它是一种很好的方式来模拟业务需求,即“当某事发生时,做这件事”。
领域事件由事件实际发生的代码部分触发。应用程序的不同部分可能会调用实体的同一方法。在某些情况下,方法是通过间接方式调用的,调用者并不知道它正在被调用。通过使用领域事件,事件将在所有情况下分发。不需要确保从所有调用方法的不同地方分发事件。
应用层(控制器、服务)可以告诉实体做什么,但无法可靠地知道动作是否实际执行,或者是否执行了额外的动作。控制器或服务可以请求 $bookshelf->removeBook($book)
,但只有 $bookshelf
才知道书是否实际上被删除。如果事件确实发生了,实体可以通过记录一个 BookRemoved
事件来告诉全世界。
一些问题可能会诱使你将服务注入到实体中。通过领域事件,你可以避免这种情况。你可以让实体分发一个事件,并设置一个监听器来响应该事件。相关的服务可以正确地作用于实体,而不是相反。
摘要
// // The event // final readonly class PostPublished { public function __construct(public string $postId) {} } // // The entity // use Rekalogika\Contracts\DomainEvent\DomainEventEmitterInterface; use Rekalogika\Contracts\DomainEvent\DomainEventEmitterTrait; class Post implements DomainEventEmitterInterface { use DomainEventEmitterTrait; // ... public function setStatus(string $status): void { $originalStatus = $this->status; $this->status = $status; // records the published event if the new status is published and it // is different from the original status if ($status === 'published' && $originalStatus !== $status) { $this->recordEvent(new PostPublished($this->id)); } } // ... } // // The listener // use Psr\Log\LoggerInterface; use Rekalogika\Contracts\DomainEvent\Attribute\AsPostFlushDomainEventListener; class PostEventListener { public function __construct(private LoggerInterface $logger) {} // will be called after the post is published and the entity manager is // flushed #[AsPostFlushDomainEventListener] public function onPostPublished(PostPublished $event) { $postId = $event->postId; $this->logger->info("Post $postId has been published."); } } // // The caller // use Doctrine\ORM\EntityManagerInterface; /** @var Post $post */ /** @var EntityManagerInterface $entityManager */ $post->setStatus('published'); $entityManager->flush(); // the event will be dispatched after the flush above, afterwards the listener // above will be called, sending a message to the logger
特性
- 开箱即用。基本功能无需配置。
- 简单、无偏见的设计。使用普通的事件对象,并且对领域实体要求不高。
- 使用标准的 Symfony 的 EventDispatcher,具有相同的分发语义和监听器注册。
- 事务支持。
- 与多个实体管理器协同工作。
- 多个被认为是相同的事件只会分发一次。
- 四种监听策略:立即、预刷新、后刷新和事件总线。
- 使用 Symfony Messenger 作为事件总线实现。
- 在发布事件到事件总线时利用事务性输出模式来保证一致性和交付。
- 利用 Symfony Scheduler 将未交付的事件转发到事件总线。
- 不要求你更改与实体交互的方式。
- 应该在没有任何更改的情况下在所有地方工作:控制器、消息处理程序、命令行等。
- 分离的合约和框架。有助于强制执行架构边界。你的领域不必依赖于框架。
- Symfony Profiler 集成。在分析器的事件面板中调试事件。
待办事项
- 支持 Doctrine MongoDB ODM。
- 支持事件继承。
- 弃用
__remove()
方法,改用带属性标记的方法。
安装
确保已启用 Symfony Flex(默认已启用)。打开命令行控制台,进入您的项目目录并执行
composer require rekalogika/domain-event
文档
许可证
MIT
贡献
rekalogika/domain-event
仓库是从主仓库分出来的只读仓库。问题和拉取请求应提交到 rekalogika/domain-event-src monorepo。