领域服务
1.为什么需要
通过领域建模,可以将领域中绝大部分的业务过程和业务规则清晰地归属到对应的实体和值对象中。然而,当领域中的某个操作涉及多个聚合根,或者不是实体和值对象的自然职责时,如果将其强加到实体或者值对象中,就会显得非常突兀,并且不合理
2.介绍
领域服务表示一个无状态的操作,领域服务是一个接口,在接口中声明不属于实体和值对象的方法,这个接口就是领域服务(Domain Service)。领域服务也是领域模型的一种表现形式
3.注意
领域服务有领域前缀,所以并不是任何一个底层操作都称为领域服务,比如计算逻辑,如果它是通用的计算而没有业务逻辑,那么就不是领域服务。如计算数组的长度、处理字符串等。但如果计算是与领域相关的,比如计算一个订单最终能享受的促销折扣,有一定的业务逻辑,且来源于通用语言,那么就是典型的领域服务
创建领域服务时要经过慎重考虑,确认真正需要将业务操作建模成领域服务,否则会造成领域服务的滥用,形成新的贫血模型
4.特点
- 领域服务是领域中的业务操作,但是该操作无法归属于实体和值对象
- 推荐根据操作来命名领域服务,如数据导出领域服务被命名为DomainExportService
- 领域服务是无状态的
- 操作过程涉及多个领域对象
- 对领域对象进行转换
- 以多个领域对象作为输入,结果产生一个值对象(如统计计算逻辑)
5.案例理解
介绍
以导出数据到Excel的领域服务,命名为DataExportDomainService
代码
public Interface DataExportDomainService {
/**
* 聚合数据导出
*/
List<Map<String,Object>> export(List<EntityId> entityIdList);
}
实现DataExportDomainService后,将在Application Service中使用:
public class ApplicationService {
@Resource
private DataExportDomainService dataExportDomainService;
public Excel exportToExcel(DataExportCommand cmd) {
// 从请求中获得选中的聚合跟的唯一标识,此时是基本数据类型
List<String> ids = cmd.getEntityIds();
// 用基本类型生成聚合根唯一标识的值对象
List<EntityId> entityIdList = this.toEntityIdList(ids);
// 根据唯一标识集合加载多个聚合根,读取这些聚合根的信息,生成渲染Excel所需要的数据
List<Map<String,Object>> excelData = dataExportDomainService.export(entityIdList);
// 调用Excel工具类生成Excel文件并返回
Excel excel = ExcelUtil.render(excelData);
return excel;
}
}
6.与应用服务的区别
- 领域服务:位于领域层,是领域模型的一部分。领域服务用于完成领域内特定的业务操作,通常这些业务操作不适合由实体或值对象来完成。领域服务是无状态的,通常涉及多个聚合根之间的协调与交互。与其他领域模型一样,领域服务需要由应用服务来调用,不能单独存在
- 应用服务:位于应用层,负责定义业务用例并协调业务用例的整体流程,同时协调领域层和基础设施层来完成业务操作。应用服务不了解业务的具体细节,也不会参与业务逻辑处理,仅仅提供领域模型和领域服务执行操作的舞台