rekalogika/domain-event

Symfony 和 Doctrine 的领域事件实现

资助包维护!
priyadi

安装次数: 1,118

依赖关系: 1

建议者: 0

安全: 0

星标: 2

关注者: 1

分支: 0

类型:symfony-bundle

2.5.0 2024-09-16 15:46 UTC

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

文档

rekalogika.dev/domain-event

许可证

MIT

贡献

rekalogika/domain-event 仓库是从主仓库分出来的只读仓库。问题和拉取请求应提交到 rekalogika/domain-event-src monorepo。