假设我的任务是使用域驱动设计(DDD)在通知发送域中构建系统。该系统的一个关键要求是它需要支持各种“类型”的通知,如短信,电子邮件等。
在开发域模型的几次迭代之后,我继续着手将Notification
基类作为实体,子类SMSNotification
,EmailNotification
等作为子类(每个都是一个实体)。
Notification
public abstract class Notification extends Entity<UUID> {
//...fields...
public abstract void send();
}
SMSNotification
public class SMSNotification extends Notification {
public void send(){
//logic for sending the SMS notification using an infrastructure service.
}
}
EmailNotification
public class EmailNotification extends Notification {
public void send(){
//logic for sending the email notification using an infrastructure service.
}
}
Notification
的每个子类都与基础结构服务进行交互,其中基础结构的任务是与某些外部系统连接。 domain services
的概念时,在第107页的“领域驱动设计”一书中专门介绍了一些关于此的页面空间:
...,在大多数开发系统中,在域对象和外部资源之间建立直接接口是很尴尬的。我们可以打造这样的外部服务,其外观采用模型方面的输入,......但无论我们拥有什么中介,即使它们不属于我们,这些服务也在履行域名责任。
SendNotificationService
,而不是在send
的每个子类上使用Notification
方法,我不知道如何避免需要知道提供了哪种类型的通知,以便采取适当的基础架构行动: SendNotificationService
(域名服务)
public class SendNotificationService {
public void send(Notification notification){
//if notification is an SMS notification...
// utilize infrastructure services for SMS sending.
//if notification is an email notification...
// utilize infrastructure services for email sending.
//
//(╯°□°)╯︵ ┻━┻)
}
}
Notification
,SMSNotification
和EmailNotification
类来首先建议模型。在send
的每个子类上实现Notification
方法是有意义的,因为需要发送所有通知(证明其在Notification
中的位置)以及Notification
的每个“类型”或子类将在发送通知的方式中具有特殊行为(证明send
中的Notification
抽象是合理的。这种方法也遵循开放/封闭原则(OCP),因为Notification
类将不再修改,并且当支持新的通知类型时,可以创建Notification
的新子类来扩展功能。 无论如何,似乎没有实体与外部服务接口,以及在DDD中根本没有实体的子类。 Notification
删除了发送通知的行为,那么放置通知的位置必须知道通知的“类型”,并采取相应的行动,我只能将其概念化为{{1}的链}语句,与OCP直接矛盾。答案 0 :(得分:1)
TLDR:如果您需要针对您的域执行某些基础架构逻辑,并且您需要从域中输入一些内容 - 不要将其构建到其中,只需使用适当的数据/标记声明意图。然后,您将在基础架构层中处理此声明的意图。
各种通知是否与其他交付机制不同?如果不是 - 可能足以使用Notification值对象(或实体,如果您的域模型需要)与附加字段(Enum,如果列表已知或某种标记)来存储传递方法名称。也许,每个通知实例可能有很多这样的方法。
然后你有一个业务逻辑 - 域服务 - 来发送通知。域服务应该仅依赖于域词汇表。 E.g NotificationDeliveryMethodProvider。
在适配器层中,您可以实现各种交付方法提供程序以与基础结构进行交互。以及根据DeliveryMethod枚举(或标记)中的值获取提供者的工厂。
基本上,它并不是发送&#34;发送&#34;的责任。本身就是以任何方式操纵。它的职责应该是维持其状态,以一致的方式执行状态转换并协调其封闭实体/值的状态。关于其状态的火灾事件发生了变化。
在我的一个项目中,我在domain
包下使用了以下子包:
provides
- 提供给客户的域服务接口cousumes
- 上游依赖的接口businesslogic
- 域名服务的实施values
- 使用代码实现其不变量的值对象除了domain
包之外,还有:
adapters
打包基础架构App
对象,其中所有接口都绑定到实现。 config
包,但在我的情况下它非常轻。 这些domain
,adapters
,App
和config
可以部署为具有明确依赖关系结构的不同jar文件,如果您需要为其他人强制执行它。
答案 1 :(得分:0)
在开发域模型的几次迭代之后,我继续着陆将Notification基类作为实体,将子类SMSNotification,EmailNotification等作为子类
这可能是一个错误。
public abstract class Notification extends Entity<UUID> {
public abstract void send();
}
几乎可以肯定的是。你可以让它运作,如果你扭曲了,但是你走错了路。
您的域模型中的实体的责任是 state 的管理。还要让实体负责在整个流程边界内调度消息的副作用,这违反了关注点的分离。所以应成为合作者。
对于Evans,正如您将注意到的,协作采用域服务的形式,它本身将与基础结构服务协作以产生所需的结果。
让实体访问域服务的最直接方式是简单地将域服务作为参数传递。
public class SMSNotification extends Notification {
public void send(SMSNotificationService sms) {
//logic for sending the SMS notification using an infrastructure service.
}
SMSNotification支持与SMSNoticationService提供商的协作,我们明确说明。
您在此处提供的界面看起来更像Command Pattern。如果你想做到这一点,你通常会在构造函数中连接特定的实现
public class SMSCommand extends NotificationCommand {
private final SMSNotificationService sms;
private final SMSNotification notification;
public final send() {
notification.send(sms);
}
}
您可以使用泛型(取决于您的语言选择)做一些事情,使这些不同服务之间的相似之处更加明显。例如
public abstract class Notification<SERVICE> extends Entity<UUID> {
public abstract void send(SERVICE service);
}
public class SMSNotification extends Notification<SMSNotificationService> {
public void send(SMSNotificationService service){
//logic for sending the SMS notification using an infrastructure service.
}
}
public class NotificationCommand<SERVICE> {
private final SERVICE service;
private final Notification<SERVICE> notification;
public final send() {
notification.send(service);
}
}
这是主要方法。
有时适合的替代方法是使用穷人的模式匹配。不是传递特定类型实体所需的特定服务,而是将它们全部传递给....
public abstract class Notification extends Entity<UUID> {
public abstract void send(SMSNotificationService sms, EmailNotificationService email, ....);
}
然后让每个实现精确选择它需要的东西。我不希望这种模式在这里成为一个很好的选择,但它是一个偶尔有用的俱乐部。
您有时会看到的另一种方法是在构造实体时将所需的服务注入实体
SMSNotificationFactory {
private final SMSNotificationService sms;
SMSNotification create(...) {
return new SMSNotification(sms, ...);
}
}
再一次,一个好的俱乐部可以放入包中,但不适合这个用例 - 你可以做到,但突然有很多额外的组件需要知道通知服务,以便将它们带到他们需要的地方是。
notification.send(service)和service.send(通知)之间的最佳选择
可能
notification.send(service)
使用&#34;告诉,不要问&#34;作为理由。您将协作者传递给域实体,它决定(a)是否要进行协作,(b)传递给域服务的状态,以及(c)如何处理返回的任何状态。
SMSNotification::send(SMSNotificationService service {
State currentState = this.getCurrentState();
{
Message m = computeMessageFrom(currentState);
service.sendMessage(m);
}
}
在边界处,申请不是object oriented;我怀疑当我们从域的核心向域移动时,我们看到实体让位于值让位于更原始的表示。
事实上,这有点纠结。域服务的一个动机是将域模型与IO分离 - 所有IO关注点都由域服务实现(或者更可能由域服务与之协作的应用程序/基础结构服务)处理。就实体而言,所涉及的方法只是一种功能。在阅读了纯粹的域模型之后,事实上不应该有任何IO,我不再确定
另一种方法是在关注点之间创造更多分离;您将两部分之间的编排显式化
List<SMSRequest> messages = domainEntity.getMessages();
List<SMSResult> results = sms.send(messages)
domainEntity.onSMS(results)
在这种方法中,所有IO都发生在短信服务本身内;与模型的交互被限制在内存表示中。您已经有效地获得了一个协议,该协议可以管理模型中的更改以及边界处的副作用。
我觉得Evans建议将service.send(通知)作为界面。
我认为,课程的马匹 - 将实体传递给负责协调模型中多个变更的域服务是有道理的。在更改为聚合的上下文中,我不会选择用于将状态与边界进行通信的模式。
答案 2 :(得分:0)
我同意你的看法Notification
的主要责任应该是,它可以发送自己。这就是它存在的全部原因,因此它是一个很好的抽象。
public interface Notification {
void send();
}
此接口的实现您正在寻找的基础结构服务。他们不会(不应该)直接被其他&#34;业务&#34;或&#34;核心&#34;类。
关于在Entity
中制作的注意事项:我自己阅读蓝皮书的内容是,DDD 不关于使用实体,服务,聚合根等等。主要观点是无所不在的语言,上下文,如何使用域本身。 Eric Evans自己说这种想法可以应用于不同的范例。它不必总是涉及相同的技术事物。
注意&#34;传统&#34;设计来自其他评论(@VoiceOfUnreason):至少在面向对象中,&#34;持有状态&#34;不是一个真正的责任。责任只能直接来自无所不在的语言,换言之,来自企业。 &#34;传统的&#34; (即程序)设计分离数据和功能,面向对象完全相反。因此,请务必确定您的目标范围,然后选择解决方案可能更容易。