我正在使用具有以下层的Entity Framework 6处理WinForms应用程序:
当用户从UI单击“保存”按钮时,它会调用“应用程序”层中的应用程序服务并传入请求。然后,应用程序服务使用该请求调用域服务。域服务调用域模型中的多个实体,以对请求中使用的数据执行验证。
域模型中的一个或多个验证需要来自存储库的信息,以确定从表示层接收的请求中的数据是否符合某些业务规则。
我正在考虑两个方案来解决这个问题。
让Application Service检索所需的信息 用于验证的存储库并将这些值传递给 域服务将调用域模型和实体 验证传入的规则和值请求。然后让 应用程序服务在域服务具有时保存请求 完成了验证,这将导致返回控制 返回同步等待的应用程序服务 完成验证。如果我这样做,那么域层将 没有对存储库的直接或间接(注入)引用。 如果我这样做,域服务的单元测试将更容易 因为没有注入任何东西来执行验证。 它所需要的一切都已经传入。缺点是有些 因为现在,业务知识被放入应用服务中 它需要知道要检索哪个存储库信息 请求的验证。
调用域服务以验证请求时, 将Application Service的实例注入其中。域名 然后,服务可以使用存储库从存储库中获取信息 注入Application Service,其服务合同定义于 域层。一旦所有信息都可用,就会通过 根据需要,各种实体验证规则和价值。一旦 验证完成后,域服务使用保存请求 注入的应用程序服务。域服务完成后 并退出,它将保存操作的状态返回给 已经等待验证的应用程序服务 完成。外部等待应用程序服务然后可以返回 保存到UI的结果。我在这里关注的一个问题是什么时候 单元测试域服务我将不得不模拟注入 申请服务。
哪种选择或其他行动方案会更好?提前谢谢。
答案 0 :(得分:5)
"应该将应用程序服务注入域服务"
不,永远不会!
解析来自应用程序服务的数据并将其传递给域服务通常很好,但如果您认为域逻辑泄漏那么 您可以应用接口隔离原则(ISP),并根据查询"所需数据"所需的合同在域中定义接口。在您的存储库或任何其他可以完成任务的对象上实现该接口,并将其注入您的域服务。
E.g。 (伪代码)
//domain
public interface WantedDataProvider {
public WantedData findWantedData(...) {}
}
public class SomeDomainService {
WantedDataProvider wantedDataProvider;
}
//infrastructure
public class SomeRepository implements WantedDataProvider {
public WantedData findWantedData(...) {
//implementation
}
}
编辑:
我有一个带有员工姓名的请求聚合根。一条规则是 员工必须是全职而非承包商
如果聚合中已存在执行验证的信息,您也可以将此AR用作其他AR的工厂。假设员工持有合同类型......
Employee employee = employeeRepository.findById(employeeId);
Request request = employee.submitRequest(requestDetails); //throws if not full time
requestRepository.add(request);
请注意,除非您更改聚合边界,否则只能在此处使不变量最终保持一致,但与其他解决方案相同。
答案 1 :(得分:1)
当域中的重要流程或转换不是实体或值对象的自然责任时,将操作作为声明为服务的独立接口添加到模型。根据模型的语言定义接口,并确保操作名称是泛在语言的一部分。使服务无国籍 - 埃文斯,蓝皮书
不要倾向于将域概念建模为服务。只有在情况适合时才这样做。如果我们不小心,我们可能会开始将服务视为我们的模型“银弹”。过度使用服务通常会导致创建Anemnic域模型的负面后果,其中所有域逻辑都驻留在服务中。 - 弗农,红皮书
我想通过广泛的引用来说明,您似乎将域服务视为您必须拥有的东西,而您绝对不必这样做。您的应用程序服务可以愉快地使用存储库来获取聚合,然后调用聚合根方法来执行必要的操作。您的聚合根负责通过在其中执行必要的验证来保护自己的不变量,并在出现任何问题时抛出验证异常。
如果你绝对必须使用域服务,如果你做得对,意味着你实现洋葱架构,内层不知道外层,你的域服务将无法知道应用服务。但是,如果您必须,您可以将代理发送到域服务,以执行应用程序服务所需的操作。但是,这种情况太复杂了。鉴于您只需要验证,请阅读蓝色和红色书籍的引用部分并做出正确的决定。同样,DDD中没有强制域服务,这是常见的误解之一。