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

woo*_*oof 14 domain-driven-design

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

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

public interface UserApplicationService {
  void registerUser(UserDTO userDTO);
  List<UserDTO> getUsersForOrganization(String organizationId);
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

2)域类型

public interface UserApplicationService {
  void registerUser(User user);
  List<User> getUsersForOrganization(OrganizationId organizationId);
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

3)域对象+门面

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

Yug*_*hou 6

+1

我更喜欢混合解决方案.

1)我使用域对象作为参数,但仅限于ValudObject.我认为应该仔细管理实体的生命周期,并且大多数时候视图没有足够的值来填充整个实体,除了非常简单的CRUD应用程序.我多次看到一些开发人员不小心地通过构造函数初始化实体,并且只使用了特定函数所需的部分字段来填充它们,这使得很容易为NullPointerException引入错误,并且分配给修复问题的穷人已经搜索数十种方法以查找创建实体的位置.实体 s从Repository中检索或由Factory在我的项目中创建.

有时我会使用一些表单对象作为参数以简化.

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

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

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

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

UPDATE1:

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

2)交易边界在申请服务上.从技术上讲,域对象可能会无意中被修改出边界.我们通过团队纪律和代码审查来涵盖这一点.