Php 在域驱动设计中,如何确定发送电子邮件是应用程序级别还是域级别的问题?
假设您有一个名为“Next-in-Line”的简单应用程序。任何排队等候时间较长的企业都可能希望您的软件为客户提供更好的排队体验 干系人说:“当客户想要一张票时,他们会走到我们的电脑控制台并输入他们的电子邮件地址。然后他们会收到一封电子邮件,其中包含他们的票号和登录到我们的应用程序的唯一访问权限,以获得更多的票队列功能”。发送带有票号(以及应用程序访问权限)的电子邮件是业务不可或缺的一部分 我们的命令处理程序(应用程序级)可能如下所示:Php 在域驱动设计中,如何确定发送电子邮件是应用程序级别还是域级别的问题?,php,email,events,domain-driven-design,Php,Email,Events,Domain Driven Design,假设您有一个名为“Next-in-Line”的简单应用程序。任何排队等候时间较长的企业都可能希望您的软件为客户提供更好的排队体验 干系人说:“当客户想要一张票时,他们会走到我们的电脑控制台并输入他们的电子邮件地址。然后他们会收到一封电子邮件,其中包含他们的票号和登录到我们的应用程序的唯一访问权限,以获得更多的票队列功能”。发送带有票号(以及应用程序访问权限)的电子邮件是业务不可或缺的一部分 我们的命令处理程序(应用程序级)可能如下所示: class TakeTicketCommandHandle
class TakeTicketCommandHandler
{
private $repo;
public function __construct(TicketRepository $repo)
{
$this->repo = $repo;
}
public function handle(TakeTicketCommand $command)
{
$ticket = new Ticket(new EmailValueObject($command->getEmail()));
DB::transaction(function () {
// Send email here from the command handler?
// Seems domain "leaky" because Ticket Aggregate Root is responsible for this?
...
$this->repo->persist($ticket);
}
}
}
class Ticket extends AggregateRoot
{
private $id;
private $email;
public function __construct(EmailValueObject $email)
{
$this->id = new GUID();
$this->email = $email;
// Maybe some more domain logic here
...
// Send email here?
// Should I have injected an interface for sending an email here?
...
}
}
我们的票证聚合根可能如下所示:
class TakeTicketCommandHandler
{
private $repo;
public function __construct(TicketRepository $repo)
{
$this->repo = $repo;
}
public function handle(TakeTicketCommand $command)
{
$ticket = new Ticket(new EmailValueObject($command->getEmail()));
DB::transaction(function () {
// Send email here from the command handler?
// Seems domain "leaky" because Ticket Aggregate Root is responsible for this?
...
$this->repo->persist($ticket);
}
}
}
class Ticket extends AggregateRoot
{
private $id;
private $email;
public function __construct(EmailValueObject $email)
{
$this->id = new GUID();
$this->email = $email;
// Maybe some more domain logic here
...
// Send email here?
// Should I have injected an interface for sending an email here?
...
}
}
问题:
ticketquedevent
。在此事件的事件处理程序中,将更新另一个Aggegrate根。这是一种糟糕的做法/造型吗?如果应用程序级服务旨在协调域,那么通过事件聚合根到根通信是否是DDD的有效方法至于3个具体问题,我将尽可能回答1和2,并将3个问题分开处理。 域负责解决所有业务需求。必须发送电子邮件是此业务需求的一部分,因此域的责任 问题是我们需要一些外部组件来执行邮件的发送,这显然是我们不想弄乱我们的域的 首先,交易的问题: 正如您提到的,它可以回滚(或转发/提交)。 整个操作应作为一个工作单元处理。因此,所有操作都应该失败或通过,包括电子邮件。 通常,您希望应用程序尽快响应用户的请求,而不是让他在某个电子邮件服务器发送电子邮件时等待。因此,我们倾向于编写一个条目(按照CQRS命令行)来指定电子邮件的所有详细信息,并从每10秒左右运行一次的单独工作任务中处理这些信息 责任或逻辑应该在哪里: 有几种方法可以解决这个问题 一, 我通常会让域知道
INotifyService
的接口,它不知道通知客户的实现。我使用适配器模式进行实现,它可以使用通信适配器,例如:Sms、电子邮件等。这样,当添加实现时,或者客户可以选择使用特定的通信方法时,我们可以简单地使用所需的适配器,并且调用代码保持不变。
我建议通过双重分派在域中使用INotifyService,因为它可以使域实体更干净
二,
另一种选择是使用应用程序服务层,在域操作完成后显式处理发送。
问题是,您的逻辑现在是域逻辑的辅助工具,每次执行域操作时,您需要发送电子邮件,您必须记住调用该方法。与前面的以领域为中心的解决方案类似,您需要一个在“原子工作单元”期间受控制的工作单元。
当您有一个web应用程序时,这通常非常容易。您可以阅读有关应用程序服务层的更多信息:
希望这有点帮助
您是否应该使用通知机制
这就是问题3
那完全取决于你。
我发现,不提出事件并对其作出反应有时很难追踪。如果您不知道系统是如何组合在一起的,那么了解代码的执行顺序就有点困难了
诚然,随着时间的推移,你会习惯它,而且它也会发挥作用
希望这一切都有意义
您展示的示例没有业务逻辑,在本示例中遵循DDD将使代码过于复杂
干系人的电子邮件要求应该如何在领域驱动的设计应用程序中实现
通过发布事件并通过电子邮件订阅此电子邮件
基础设施服务
我们如何知道什么时候是应用程序级需求,什么时候是域级需求
域-业务逻辑。
基础设施-抽象技术问题(数据库、电子邮件)。
应用程序-事务、高级日志记录和安全性
请注意,电子邮件位于事务中,但是事务在此回滚以实现持久性,而不是像电子邮件这样的服务。如果票证持久性失败,我们可以如何处理电子邮件?(我们是否应该提供撤销操作,比如发送另一封电子邮件通知用户错误?)
若业务部门要求通知客户,您可以创建EmailrRevent,若不只是在应用程序级别进行错误日志记录
假设我们想让Ticket也引发类似TicketQueedEvent的事件。在此事件的事件处理程序中,将更新另一个Aggegrate根。这是一种糟糕的做法/造型吗?如果应用程序级服务旨在协调域,那么通过事件聚合根到根通信是否是DDD的有效方法
发送事件是一种有效的方法。根之间的一致性可以
这可能是个问题。游寿