过度使用领域服务将导致贫血领域模型,即所有业务逻辑都位于领域服务中,而非实体和值对象。
来看使用领域服务案例:
User认证
考虑身份与访问上下文,对一个User进行认证。
- 系统必须对User进行认证,并且只有当Tenant处激活状态时才能对User进行认证。
为什么领域服务在此时是必要的呢?难道不可以简单地将该认证操作放在实体?从客户角度来看,我们可能会使用以下代码实现认证:
// client finds User and asks it to authenticate itself
boolean authentic = false;
User user = DomainRegistry
.userRepository()
.userWithUsername(aTenantld, aUsername);
if(user != null) {
authentic = user.isAuthentic(aPassword);
}
return authentic;
以上设计至少存在如下问题
- 客户端需要知道某些认证细节
他们需要找到一个User,然后再对该User进行密码匹配
- 这种方法也不能显式表达通用语言
这里,我们询问的是一个User "是否被认证”,而没表达“认证”这个过程。在有可能的情况下,我们应尽量使建模术语直接表达出团队成员的交流用语,但还有更糟糕的。
这种建模方式并不能准确表达出团队成员所指的“对User进行认证”的过程。它缺少了 “检查Tenant否处于激活状态”这个前提条件。如果一个User所属的Tenant处于非激活状态,我们便不应该对该User进行认证。
或许可以通过以下方法予以解决:
这种方式的确对Tenant的活跃性做了检查,同时我们也将User#isAuthentic换成Tenant#authenticate
但这种方式也有问题:
- 此时客户端需知道更多认证细节,而这些他们不应该知道。当然,可以将
Tenant#isActive放在authenticate,但这并不是一个显式的模型。同时这将带来另外一个问题,即此时的Tenant需要知道如何对密码进行操作。
回忆一下该认证过程的另一个需求:
- 必须对密码进行加密,并且不能使用明文密码
对于以上解决方案,我们似乎给模型带来了太多的问题。对于最后一种方案,我们必须从以下解决办法中选择一种:
-
在Tenant中处理对密码的加密,然后将加密后的密码传给User。这种方法违背了单一职责原则
-
由于一个User必须保证对密码的加密,它可能已经知道了一些加密信息。如果这样,我们可在User上创建一个方法,该方法对明文密码进行认证。但这种方式下,认证过程变成了Tenant的门面(Facade),而实际的认证功能全在User。另外,User的认证方法必须