【发布时间】:2017-11-13 00:53:54
【问题描述】:
背景
假设我的任务是使用领域驱动设计 (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 的概念时,Eric Evans 在他的《领域驱动设计》一书的第 107 页上专门用了一点页面空间:
...,在大多数开发系统中,在域对象和外部资源之间建立直接接口是很尴尬的。我们可以用一个根据模型接受输入的外观来装扮这样的外部服务,......但是无论我们可能拥有什么中介,即使它们不属于我们,这些服务也正在履行域责任...... .
- 如果相反,我使用 Evans 的建议在我的域模型中获取
SendNotificationService,而不是在Notification的每个子类上使用send方法,我不确定如何避免需要了解 提供了什么类型的通知,以便可以采取适当的基础设施操作:
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类。在Notification的每个子类上实现send方法是有意义的,因为所有通知都需要发送(证明其在Notification中的位置)并且Notification的每个“类型”或子类将具有特殊的行为方式通知已发送(证明在Notification中制作send是合理的)。这种方法还遵循开放/封闭原则 (OCP),因为Notification类将禁止修改,并且随着支持新的通知类型,可以创建Notification的新子类来扩展功能。 不管怎样,似乎在没有实体与外部服务接口以及在 DDD 中根本没有实体的子类方面达成了共识。 - 如果从
Notification中删除发送通知的行为,那么它的放置位置必须知道通知的“类型”,并采取相应的行动,我只能将其概念化为if...else...语句链,它直接与 OCP 相矛盾。
【问题讨论】:
-
答案是:永远不要在域实体中注入任何东西。域实体应该照顾它自己的逻辑。不适合实体的所有其他逻辑都应该在域服务中实现,这应该是非常罕见的。如果您想采用这种方法,即实体负责在发生某些事情时进行通知,您应该转向基于事件的架构,在这种架构中,域事件和域处理程序会触发通知。
-
@FreerFactor 与技术能力紧密耦合的领域是最难建模的领域之一。对您的业务问题的 3 行描述并没有告诉我们您打算如何处理这些通知,而不是发送通知、对它们采取行动的用例是什么,以及最终是否使用 DDD 战术模式或其他方式。
-
如果我们坚持您对问题域的描述,您可以使用简单的 CRUD,甚至可以依赖外部技术平台的日志记录和审核,然后对您的通知的良好历史感到满意。
-
@guillaume31 我对文档模板也有同样的疑问,
DocumentTemplate有一些状态,但大多数行为都是技术性的。我想知道我是否应该做类似docTemplate.generate(service)、service.generate(docTemplate)之类的事情,甚至让docTemplate持有对在基础设施中实现的TemplateFile接口的引用,其中每种模板都有一个(直接服务参考) ?如果我根本没有对域模型进行建模,我应该只使用普通结果集和所有逻辑到服务中吗?没有领域模型就无法思考...... -
也许实体(好)与服务(坏)是错误的二分法,而 DDD 是错误的角度。将
DocumentGenerator称为“服务”是否有意义?如果模板管理部分非常简单,你不能将它建模为 CRUD 并将技术生成部分实现为普通的旧非 DDD 对象吗?如果模板部分很复杂,为什么不使用两个 BC - 一个用于模板,一个用于生成?