定制 lingoda/domain-events 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

lingoda/domain-events

Composer 安装命令:

composer require lingoda/domain-events

包简介

Lingoda Domain Events Bundle

README 文档

README

Installation

composer req lingoda/domain-events

Bundle configuration

# 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

Usage

IMPORTANT NOTE: Never record domain events in doctrine lifecycle hooks!

Example of simple User entity that triggers a Domain Event.

Sample Domain Event

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;
    }
}

Sample User entity that records the event

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;
    }
}

In action

// 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

Dispatching domain events with Messenger Worker

First configure the outbox messenger transport

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

Skip Locked

When running multiple consumers concurrently, you can enable SKIP LOCKED to avoid row contention. Instead of consumers blocking each other on locked rows, each consumer will skip already-locked rows and pick the next available one.

This requires MySQL 8.0+ or PostgreSQL 9.5+.

Enable it via the DSN:

framework:
  messenger:
    transports:
      outbox:
        dsn: 'outbox://default?skip_locked=true'

Or via transport options:

framework:
  messenger:
    transports:
      outbox:
        dsn: 'outbox://default'
        options:
          skip_locked: true

After that we can consume the Outbox table and dispatch domain events from it with the below command

bin/console messenger:consume outbox

Scheduling events

We can schedule Domain Events to be published in the future

// 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

Replacing/Re-scheduling events in the event_store

We can replace/re-schedule unpublished events by implementing the ReplaceableEventInterface for the Domain Event If you implement this interface, before the OutboxRecord persister stores a new domain event, it will check if there is any previously stored but unpublished events from the same entity id, if yes it will delete them and add the new one only.

Enriching Domain Events

While domain events should be immutable, sometimes it's inevitable that you need to enrich with additional information but you don't want to assign at creation time because the service is not accessible inside the entity.

You can listen to the PreAppendEvent in a subscriber/listener that is dispatched right before the Domain Event gets persisted. At this point you can enrich with additional information.

Simple example would be injecting and actorId which corresponds to the user id that is currently interacting with the app.

Testing

Install dev dependencies

# Install dev dependecies
composer install --dev

Run tests

vendor/bin/phpunit
vendor/bin/phpspec run

TODO

  • Add functional tests
  • Add instructions for doctrine mapping and routing DomainEvent
  • Fix issues around Carbon serialization

lingoda/domain-events 适用场景与选型建议

lingoda/domain-events 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 28.96k 次下载、GitHub Stars 达 8, 最近一次更新时间为 2022 年 06 月 10 日, 在 PHP 生态内属于活跃度较高的组件。

我们在过去多个企业项目中使用过 lingoda/domain-events 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 lingoda/domain-events 我们能提供哪些服务?
定制开发 / 二次开发

基于 lingoda/domain-events 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 28.96k
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 8
  • 点击次数: 22
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 8
  • Watchers: 20
  • Forks: 3
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2022-06-10