应用程序服务参数/返回类型

时间:2013-07-31 19:59:04

标签: domain-driven-design

我使用围绕DDD概念组织的域来处理标准Web应用程序。我想知道我的应用程序服务应该接受和返回什么样的对象。假设我有User聚合的应用服务。

1)DTO /简单类型(字符串,整数等)

public interface UserApplicationService {
  void registerUser(UserDTO userDTO);
  List<UserDTO> getUsersForOrganization(String organizationId);
}

在这种情况下,应用程序服务负责调用汇编程序将DTO转换为域对象,反之亦然。

这种方法的优点是我的应用程序服务是我的域对象的明确边界。另一个是应用服务是一个明确的事务边界。由持久性上下文管理的域对象不会泄漏到事务之外的某处。

缺点是,在表单的情况下,验证必须基于DTO。所以我的验证规则在域(对象负责其状态)和DTO验证规则之间重复。 (如同Spring MVC sample application)。此外,如果视图的某些部分需要另一种形式的模型(假设UserDTO没有足够的信息来呈现视图),我将需要创建另一个DTO并基于从应用程序服务返回的几个DTO,构成另一个,由view使用。

2)域类型

public interface UserApplicationService {
  void registerUser(User user);
  List<User> getUsersForOrganization(OrganizationId organizationId);
}

在这种情况下,控制器/演示者负责转换。

最大的缺点是我的域对象从应用程序服务泄漏 - 没有明确的分离。另外,我们的交易边界在哪里?可能附加到的域对象(例如Hibernate会话)在应用程序服务层之外泄漏。 (但是,我注意到这是编写了多少个示例应用程序。)

优点可能是控制器/演示者负责准备视图模型,因此它可以根据视图要求构成DTO。例如,视图可能需要一些未在DTO中从#getUsersForOrganizationMethod返回的其他信息。此外,验证可能基于域对象,因此在DTO和域对象之间不会重复。

3)域对象+门面

这是DDDsample application中使用的第三个选项。应用程序服务返回域类型,但有一些外观负责转换。所以在我的情况下,控制器/演示者使用DTO与外观进行对话,外观进行转换,并使用域对象与应用程序服务进行对话。然而,在我的拙见中,它看起来有点压倒性 - 层数太多,样板代码太多。对于一个应用程序服务,它可能听起来很棒,但如果我们有几十个,我们需要有相同数量的外观方法 - 纯复制。此外,交易边界在哪里?

3 个答案:

答案 0 :(得分:6)

+1

我更喜欢混合解决方案。

1)我使用域对象作为参数,但仅限于 ValueObject 。我认为应该谨慎管理实体的生命周期,并且大多数时候视图没有足够的值来填充整个实体,除了非常简单的CRUD应用程序。我多次看到一些开发人员不小心地通过构造函数初始化 Entity ,并且只使用了特定函数所需的部分字段来填充它们,这使得很容易为NullPointerException和穷人提供bug。被指派修复问题必须搜索数十种方法,以找到创建实体的位置。 实体是从我的项目中的存储库检索或由工厂创建的。

为简单起见,有时我会使用一些表单对象作为参数。

2)我使用mvc Controller将应用程序服务返回的域对象转换为ViewAdapter(一个组件从ui中分离域模型),有时候需要在这里完成工作。

3)仅当需要通过Web服务等远程过程调用公开应用程序服务时,才使用 Facade 。在这种情况下, Dto 用作参数和返回类型, Facade 负责转换 DTO 域模型

4)如果应用程序服务需要同时暴露给Web视图和远程过程调用,则验证很麻烦。这会导致在表单对象和Dtos上实现重复验证。我只验证简单约束(非空,长度仅举几例,业务规则由域对象以编程方式验证)因为我还没有找到完美的解决方案。

希望这会有所帮助,如果有更好的解决方案,请告诉我。

<强> UPDATE1:

1)我必须承认我不是这个领域的大师,我也想找到一个好的解决方案。因此,有时在我当前的解决方案中存在一些不一致之处,例如您在评论中提到的表单bean。有时我们将一些表单bean作为Command并在其中放置一些域逻辑,因此在这种情况下这些命令属于域层。

2)交易边界在应用服务上。从技术上讲,域对象可能会无意中被修改出边界。我们通过团队纪律和代码审查来解决这个问题。

答案 1 :(得分:3)

我经常发现自己倾向于接近方法,使用命令和查询。

这是我本周末发布的一篇博文的片段。

  

命令可以帮助您明确地支持无处不在的语言   在系统边界捕获用户意图 - 考虑使用   案例。它们充当您域内的一层,将内部分离   从外面,让你逐步介绍概念   在里面,没有打破外面。命令执行器给你一个   很好的管道,你可以利用集中安全,   性能指标,日志记录,会话管理等。并且,   如果这是你的事情 - 命令可以序列化处理   异步。

你可以在我的博客上找到一个这样的例子; http://www.jefclaes.be/2013/01/separating-command-data-from-logic-and.html

关于您对验证的疑虑,请记住,重复验证并不可怕; http://gorodinski.com/blog/2012/05/19/validation-in-domain-driven-design-ddd/

答案 2 :(得分:0)

我使用了所有这三种方法,但是以一种非常一致的方式遵循Eric Evans在DDD blue book中描述的分层原则。

对应用程序层的输入始终是简单类型(或封装它们的对象参数),输出始终是域类型,表示层处理与视图类型(DTO)的映射。

我在这里回答一个非常相似的问题时解释了我采用这种方法的原因:https://stackoverflow.com/a/41230822/509891