设计:当域对象和服务对象之间的界限不明确时

TTa*_*Tar 7 java oop domain-driven-design

这个问题的核心是一个设计问题.我将使用Java/Java EE示例来说明问题.

考虑一个使用JPA构建的Web邮件应用程序,用于服务层的持久性和EJB.假设我们的EJB中有一个服务方法,如下所示:

public void incomingMail(String destination, Message message) {
    Mailbox mb = findMailBox(destination); // who cares how this works
    mb.addMessage(message);
}
Run Code Online (Sandbox Code Playgroud)

这似乎是一种合理的商业方法.据推测,邮箱对象仍将被附加,它将无缝地将更改保存回数据库.毕竟,这是透明持久性的承诺.

邮箱对象将具有此方法:

public void addMessage(Message message) {
    messages.add(message);
}
Run Code Online (Sandbox Code Playgroud)

这里变得复杂 - 假设我们想要其他邮箱类型.假设我们有一个自动响应发件人的AutoRespondingMailbox,以及一个HelpDeskMailbox,它会在收到每封电子邮件时自动打开一个帮助台票证.

自然要做的是扩展Mailbox,其中AutoRespondingMailbox具有以下方法:

public void addMessage(Message message) {
    String response = getAutoResponse();
    // do something magic here to send the response automatically
}
Run Code Online (Sandbox Code Playgroud)

问题是我们的Maibox对象及其子类是"域对象"(在本例中,也是JPA实体).Hibernate人(以及许多其他人)宣扬非依赖域模型 - 即不依赖于容器/运行时提供的服务的域模型.这种模型的问题是AutoRespndingMailbox.addMessage()方法无法发送电子邮件,因为它无法访问,例如,JavaMail.

HelpDeskMailbox会出现完全相同的问题,因为它无法访问WebServices或JNDI注入以与HelpDesk系统进行通信.

因此,您不得不将此功能放在服务层中,如下所示:

public void incomingMail(String destination, Message message) {
    Mailbox mb = findMailBox(destination); // who cares how this works
    if (mb instanceof AutoRespondingMailbox) {
        String response = ((AutoRespondingMailbox)mb).getAutoResponse();
        // now we can access the container services to send the mail
    } else if (mb instanceof HelpDeskMailbox) {
        // ...
    } else {
        mb.addMessage(message);
    }
}
Run Code Online (Sandbox Code Playgroud)

必须以这种方式使用instanceof是问题的第一个迹象.每次要子类化邮箱时必须修改此服务类是另一个问题的迹象.

有没有人有关于如何处理这些情况的最佳做法?有人会说Mailbox对象应该可以访问容器服务,这可以通过一些捏造来完成,但它肯定会与JPA的预期用法相悖,因为容器提供除了实体之外的所有依赖注入,清楚地指示这不是一个预期的用例.

那么,我们期望做什么呢?提升我们的服务方法和放弃多态性?我们的对象自动降级为C风格的结构,我们失去了OO的大部分好处.

Hibernate团队会说我们应该在域层和服务层之间拆分业务逻辑,将所有不依赖于容器的逻辑放入域实体中,并将​​所有依赖于容器的逻辑放入服务层.我可以接受,如果有人可以给我一个如何做到这一点的例子,而不必完全放弃多态性并诉诸于instanceof和其他这样的肮脏

Har*_*lby 6

你错过了一些东西:Mailbox对象依赖于运行时提供的接口是完全合理的."不依赖于运行时服务"是正确的,因为您不应该具有编译时依赖性.

由于唯一的依赖是接口,您可以使用像StructureMap,Unity等IoC容器来为对象提供测试实例而不是运行时实例.

最后,AutoRespondingMailbox的代码可能如下所示:

public class AutoRespondingMailbox {
    private IEmailSender _sender;

    public AutoRespondingMailbox(IEmailSender sender){
        _sender = sender;
    }

    public void addMessage(Message message){
        String response = getAutoResponse();
        _sender.Send(response);
}
Run Code Online (Sandbox Code Playgroud)

请注意,此类确实取决于某些内容,但它不一定由运行时提供 - 对于单元测试,您可以轻松提供写入控制台的虚拟IEmailSender等.此外,如果您的平台发生更改或需求发生更改,则可以轻松地在构造上提供使用与原始方法不同的方法的不同IEmailSender.就是"限制依赖"态度的原因.


Ste*_*owe 4

邮箱就是邮箱...

...但是自动回复邮箱是附加了一些规则的邮箱;这可以说不是邮箱的子类,而是控制一个或多个邮箱和一组规则的 MailAgent。

警告:我对 DDD 的经验有限,但这个例子给我的印象是基于一个错误的假设,例如应用规则的行为属于邮箱。我认为对邮件应用规则与邮箱无关,即收件人邮箱可能只是过滤/路由规则使用的标准之一。因此,在这种情况下,ApplyRules(消息)或ApplyRules(邮箱,消息)服务对我来说更有意义。