先说结论:我个人认为不是的。
悖论
网上很多文章给出的结论:
- 推荐聚合划分越细越好;
- 聚合之间的编排应该放在应用层完成;
但是这就出现了一个悖论,即一些核心的领域逻辑分在了两个聚合中,但是却只能在应用层去组合。造成了领域能力的外泄。
举例
举个“登录操作”例子:
登录时,有“租户”和“用户名/密码”。用户登录时,首先判断是否在某个租户下,然后在判断该租户下用户名/密码是否相同。
因为可以增删改租户信息,也可以增删改用户信息,所以租户和用户应该划分为两个聚合。
领域建模图.png那么登录这一个行为:具体到代码就会变成了:
boolean authentic=false
//获取租户
Tenant tenant= TenantRepo.tenantOfId(aTenantId);
if(tenant!=null && tenant.isActive()){
User user=UserRepo.userWithUserName(aTenantId,aUserName);
if(user!=null){
authentic=user.isAuthentic(aPassword);
}
}
但是仔细想下,这个行为应该属于“认证授权”的行为,需要放在应用层进行编排处理吗?它会使得调用者的逻辑非常复杂。强加在客户端上的职责应该在我们的领域模型中予以解决。只与领域相关的信息决不能泄露到客户端。即使客户端是一个应用服务,它也不应该负责身份与访问权限的管理。
客户端(应用层)需要处理的唯一业务职责:调用单个业务操作,而由改业务操作去处理所有的业务细节。
解决方案:提供领域服务
提供一个领域服务,来聚合多个领域对象。
UserDescriptor userDescriptor=authenticationDomainService.authenticate(aTenantId,aUserName,aPassword);
客户端只需要获取一个无状态的AuthenticationDomainService,然后调用authenticate,这种方式将所有的认证细节放在领域服务,而非应用服务。在需要的情况下,领域服务可使用任何领域对象完成操作,包括对密码的加密。
客户端无需知道任何的细节。
通用语言也得到了满足,因为我们将所有的领域术语都放在了身份管理这个领域,而非一部分放在领域模型,另一部分放在客户端(应用层)。
领域服务方法返回了一个UserDescriptor值对象,这是一个很小的对象,并且是安全的,与User相比,它值包含了3个关键属性。
public class UserDescriptor{
private String emailAddress;
private long tenantId;
private String userName;
}
进阶:何时应该使用领域服务
领域服务到底是什么?当领域中某个操作或者转换过程不是实体或者值对象的职责时,此时便应该将该操作放在一个单独的接口,即领域服务。
请保证领域服务和通用语言是一致的,并且保证它是无状态的;
通常领域模型主要关注特定某个领域的业务,同样,领域服务也具有相似特点。由于领域服务有可能在单个原子操作中处理多个领域对象,这将增加领域服务的复杂性。
那么何时一个操作不属于实体或值对象?即何时可使用领域服务:
- 执行一个显著的业务操作过程;
- 对领域对象进行转换;
- 以多个领域对象作为输入进行计算,产生一个值对象结果;
当一个方法不便放在实体或者值对象,使用领域服务便是最佳的解决方案。
领域服务与应用服务的区别
- 应用服务并不处理业务逻辑;
- 领域服务恰恰是处理业务逻辑;
缺点:确定需要领域服务
过度使用领域服务将导致贫血领域模型,即所有的业务逻辑都位于领域服务中,而非实体和值对象。
网友评论