DDD领域驱动设计实战(六)-领域服务(中)

本文涉及的产品
密钥管理服务KMS,1000个密钥,100个凭据,1个月
简介: DDD领域驱动设计实战(六)-领域服务

案例

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进行认证。

或许可以通过以下方法予以解决:

image.png

这种方式的确对Tenant的活跃性做了检查,同时我们也将User#isAuthentic换成Tenant#authenticate

但这种方式也有问题:


此时客户端需知道更多认证细节,而这些他们不应该知道。当然,可以将

Tenant#isActive放在authenticate,但这并不是一个显式的模型。同时这将带来另外一个问题,即此时的Tenant需要知道如何对密码进行操作。

回忆一下该认证过程的另一个需求:

必须对密码进行加密,并且不能使用明文密码

对于以上解决方案,我们似乎给模型带来了太多的问题。对于最后一种方案,我们必须从以下解决办法中选择一种:


在Tenant中处理对密码的加密,然后将加密后的密码传给User。这种方法违背了单一职责原则

由于一个User必须保证对密码的加密,它可能已经知道了一些加密信息。如果这样,我们可在User上创建一个方法,该方法对明文密码进行认证。但这种方式下,认证过程变成了Tenant的门面(Facade),而实际的认证功能全在User。另外,User的认证方法必须声明为protected,以防止外界客户端对认证方法的直接调用

Tenant依赖User对密码进行加密,然后将加密后的密码与原有密码进行匹配

这种方法似乎在对象协作之间增加了额外步骤。此时,Tenant依需要知道认证细节

让客户端对密码进行加密,然后将其传给Tenant

这样导致问题在于,客户端承载了它本不应该有的职责

以上这些方法都无济于事,同时客户端依然非常复杂。强加在客户端上的职责应该在我们自己的模型中予以处理。只与领域相关的信息决不能泄漏到客户端。即使客户端是一个应用服务,它也不应该负责对身份与访问权限的管理。


客户端需要处理的唯一业务职责是:调用单个业务操作,而由该业务操作去处理所有业务细节:

image.png

简单而优雅。客户端只需获取到一个无状态的 Authenticationservice,然后调用authenticate。这种方式将所有认证细节放在领域服务,而非应用服务。在需要的情况下,领域服务可使用任何领域对象完成操作,包括对密码的加密。

客户端无需知道任何认证细节。

通用语言也得到满足,因为我们将所有领域术语都放在了身份管理这个领域,而非一部分放在领域模型,另一部分放在客户端。


领域服务方法返回一个UserDescirptor值对象,这是一个很小的对象,并且是安全的。与User相比,它只包含3个关键属性:

image.png

该UserDescriptor对象可存放在一次Web会话(Session)中。对于作为客户端的应用服务,它可进一步将该UserDescriptor返回给它自己的调用者。

如何建模领域服务?

根据创建领域服务的目的,有时对领域服务进行建模是非常简单的。你需要决定你所创建的领域服务是否需要一个独立接口。如果是,你的领域服务接口可能与以下接口相似:

package com.saasovation.identityaccess.domain.model.identity;

image.png

该接口和那些与身份相关的聚合(比如Tenant, User和Group)定义在相同的模块中,因为Authenticationservice也是一个与身份相关的概念。当前,我们将所有与身份相关的概念都放在identity模块中。该接口定义本身是简单的,只有一个 authenticate方法。

对于该接口的实现类,我们可以选择性地将其存放在不同地方。如果你正使用依赖倒置原则或六边形架构,那你可能会将这个多少有些技术性的实现类放置在领域模型外。比如,技术实现类可放置在基础设施层的某个模块中。


以下是对该接口的实现:

image.png

在对一个User进行认证时:


先根据aTenantld从Tenant的资源库中取出对应的Tenant

如果Tenant存在且处于激活状态,下一步我们将加密传入的明文密码

加密在于我们需要通过加密后的密码获取一个User。在获取一个User时,我们不但需要传aTenantld和username,还需要传入加密后的密码进行匹配(对于两个明文相同的密码,加密后也是相同的)。User的资源库将根据这三个参数来定位一个User。

如果用户提交的aTenantld, username和password都正确,我们将获得相应的User实例。但此时我们依然不能对该User进行认证,我们还需要处理最后一条需求:


只有在一个User被激活后,我们才能对该User进行认证。

即便我们通过资源库找到了一个User,该User也有可能处于未激活。通过向User添加激活功能,Tenants可从另一层面控制对User的认证。因此,认证过程的最后一步即是检查所获取到的User实例是否为null和是否处激活状态。


目录
相关文章
|
存储 自然语言处理 前端开发
领域驱动设计(DDD)-基础思想
一、序言     领域驱动设计是一种解决业务复杂性的设计思想,不是一种标准规则的解决方法。在领域驱动设计理念上,各路大侠的观点也是各有不同,能力有限、欢迎留言讨论。 二、领域驱动设计 DDD是什么 wiki释义:     领域驱动设计(英语:Domain-driven design,缩写 DDD)是一种通过将实现连接到持续进化的模型[1]来满足复杂
7534 0
|
6月前
|
消息中间件 测试技术 领域建模
DDD - 一文读懂DDD领域驱动设计
DDD - 一文读懂DDD领域驱动设计
10274 3
|
消息中间件 开发者
DDD领域驱动设计实战(六)-领域服务(上)
DDD领域驱动设计实战(六)-领域服务
455 0
DDD领域驱动设计实战(六)-领域服务(上)
|
前端开发 架构师 Java
领域驱动设计DDD从入门到代码实践
在本文中,作者将借鉴《实现领域驱动设计》的做法,介绍领域驱动设计的基本概念的同时,用一个虚拟的公司和一个虚拟的项目,把领域驱动设计进行落地实践。
13263 9
领域驱动设计DDD从入门到代码实践
|
存储 消息中间件 JSON
领域驱动设计:从理论到实践,一文带你掌握DDD!
学习DDD一个半月,最开始学习DDD的原因是因为我负责的业务线,涉及的系统非常多,想借鉴领域驱动设计的思想,看后续如何对系统进行重构。在没有学习DDD之前,感觉DDD可能属于那种“虚头巴脑”的东西,学完DDD之后,感觉。。。嗯。。。真香!
1760 0
领域驱动设计:从理论到实践,一文带你掌握DDD!
|
存储 设计模式 前端开发
浅析 DDD 领域驱动设计(1)
浅析 DDD 领域驱动设计
355 0
浅析 DDD 领域驱动设计(1)
|
设计模式 领域建模 数据库
DDD领域驱动设计落地实践系列:初识DDD
笔者在经历的很多项目中都使用了DDD领域驱动设计进行架构设计,尤其是在业务梳理、中台规划以及微服务划分等方面,DDD是重要的架构设计方法论,对平时的架构设计有非常好的指导作用。从本文开始笔者将通过一系列的文章阐述自己对于DDD的理解以及如何在项目实战中落地实践DDD。本文作为系列文章的开端,主要和大家聊聊DDD的一些基本概念以及常用方法。
DDD领域驱动设计落地实践系列:初识DDD
|
缓存 数据可视化 Java
浅析 DDD 领域驱动设计(2)
浅析 DDD 领域驱动设计
305 0
浅析 DDD 领域驱动设计(2)
|
设计模式 SQL 测试技术
一文理解 DDD 领域驱动设计!
以一种领域专家、设计人员、开发人员都能理解的通用语言作为相互交流的工具,在交流的过程中发现领域概念,然
一文理解 DDD 领域驱动设计!