本文目录导读:

在领域驱动设计(DDD)中,PHP事件驱动通常通过领域事件(Domain Events)来实现,这是DDD中用于解耦核心领域逻辑与横切关注点(如日志、通知、审计)的核心模式。
下面从几个层面详细解释在PHP中如何使用事件驱动来实现DDD。
核心概念
- 领域事件(Domain Event):领域专家关心的、发生在过去的一件事。
UserRegistered、OrderPaid。 - 事件发布器(Event Publisher):负责将事件发送给一个或多个监听器。
- 事件监听器(Event Listener):处理事件的代码,通常用于执行后续的副作用(如发送邮件、记录日志、更新读模型)。
在DDD中的典型流程
用户请求 -> 应用服务 -> 聚合根执行方法(产生事件) -> 事件发布器 -> 监听器处理
|-> 发送邮件
|-> 更新缓存
|-> 记录审计日志
PHP实现方案
使用Symfony Messenger (推荐用于生产环境)
这是PHP生态中最成熟的实现方式。
// 1. 定义领域事件(值对象)
class UserRegistered
{
public function __construct(
public readonly string $userId,
public readonly string $email,
public readonly \DateTimeImmutable $occurredOn = new \DateTimeImmutable()
) {}
}
// 2. 聚合根中记录并发布事件
class User extends AggregateRoot
{
private string $id;
private string $email;
private array $domainEvents = []; // 待发布事件
public function register(string $id, string $email): self
{
$this->id = $id;
$this->email = $email;
// 记录领域事件(重要!不要在业务方法中直接发布)
$this->domainEvents[] = new UserRegistered($id, $email);
return $this;
}
public function releaseEvents(): array
{
$events = $this->domainEvents;
$this->domainEvents = [];
return $events;
}
}
// 3. 应用服务(负责发布事件到Messenger总线)
class RegisterUserService
{
public function __construct(
private UserRepository $repository,
private MessageBusInterface $eventBus // Symfony Messenger
) {}
public function __invoke(RegisterUserCommand $command): void
{
$user = new User();
$user->register($command->userId, $command->email);
$this->repository->save($user);
// 发布所有未发布的事件
foreach ($user->releaseEvents() as $event) {
$this->eventBus->dispatch($event);
}
}
}
// 4. 事件监听器(处理副作用)
class SendWelcomeEmailListener
{
public function __invoke(UserRegistered $event): void
{
// 处理邮件发送(完全解耦)
Mailer::send($event->email, '欢迎邮件...');
}
}
// 5. 配置Messenger(services.yaml)
framework:
messenger:
routing:
'App\Domain\Event\UserRegistered': async # 异步处理
使用自定义Event Bus(轻量级方案)
适用于小型项目或不想引入消息队列的场景。
// 事件总线接口
interface EventBus
{
public function dispatch(object $event): void;
public function subscribe(string $eventClass, callable $handler): void;
}
// 使用PHP内置的in-memory事件系统
class InMemoryEventBus implements EventBus
{
private array $handlers = [];
public function dispatch(object $event): void
{
$class = get_class($event);
foreach ($this->handlers[$class] ?? [] as $handler) {
call_user_func($handler, $event);
}
}
public function subscribe(string $eventClass, callable $handler): void
{
$this->handlers[$eventClass][] = $handler;
}
}
关键设计原则
⚠️ 事务边界问题
重要原则:同一个事务内不发送事件,正确的做法是:
传递规则:
- 在聚合层:记录事件 (
$this->recordEvent(...)) - 在应用服务层:发布事件 (
$eventBus->dispatch(...)) - 原因:避免事务未提交导致的脏数据问题,如果监听器失败,不应该回滚聚合本身的持久化。
事件应具备足够的上下文
事件应该包含所有处理它所需的信息,但不包含领域私有数据。
class OrderPaid
{
public function __construct(
public readonly string $orderId,
public readonly string $customerEmail, // 足够但不包含敏感密码
public readonly Money $amountPaid
) {}
}
更复杂的场景:事件溯源
如果采用事件溯源(Event Sourcing),事件将承担更重要的角色——作为状态存储:
class BankAccount extends AggregateRoot
{
private string $accountId;
private float $balance;
public function withdraw(float $amount): void
{
if ($amount > $this->balance) {
throw new InsufficientFundsException();
}
// 记录事件(事件溯源中,这是持久化的主要数据)
$this->recordEvent(new MoneyWithdrawn($this->accountId, $amount));
}
}
在PHP DDD中使用事件驱动的最佳实践:
| 层面 | 推荐做法 |
|---|---|
| 事件定义 | 不可变的值对象,包含occurredOn时间戳 |
| 存储事件 | 在聚合根内用数组暂存(不立即发布) |
| 发布时机 | 在应用服务层,事务提交后发布 |
| 消息中间件 | Symfony Messenger (推荐)、RabbitMQ/Bridge |
| 异常处理 | 发布失败不应影响核心业务逻辑(记录错误日志) |
| 事件粒度 | 每个事件对应一个明确的业务含义(不是技术细节) |
这种模式让你能清晰地分离业务逻辑(什么时候发生事件)和副作用处理(事件发生后做什么),正是DDD所追求的核心目标。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播