【DDD 7】领域驱动设计实践 —— UI层实现

13 篇文章 2 订阅

【DDD】领域驱动设计实践 —— UI层实现

目录

【DDD】领域驱动设计实践 —— UI层实现

1. User Interface

2. Controller

controller是公司前台

controller的职责

controller的实现

BaseController

类图

 代码示例

3.  DTO

DTO是controller和service之间数据传输的载体

类图

示例代码

4.  infrastructure层的公共组件

 checkLogin

称职的门将

类图

Logging

Validation

ExceptionHandler

异常都交给UI层统一处理

类图

5. UI层类图

6. 代码示例  


 

正文

  前面几篇blog主要介绍了DDD落地架构及业务建模战术,后续几篇blog会在此基础上,讲解具体的架构实现,通过完整代码demo的形式,更好地将DDD的落地方案呈现出来。本文是架构实现讲解的第一篇,主要介绍了DDD的User Interface层的实现,详细讲解了controller、dto的职责和实现,已经UI层使用到的公共组件:CheckLogin、Loging、Validation的职责和实现细节。文末附有github地址。相比于《领域驱动设计》原书中的航运系统例子,社交服务系统的业务场景对于大家更加熟悉,相信更好理解。本文是【DDD】系列文章的其中一篇,其他可参考:使用领域驱动设计思想实现业务系统

 

1. User Interface

  User Interface层是用户接口层,为用户/调用方提供可访问的接口,我们简称为“UI”层。在 “【DDD】领域驱动设计实践 —— 框架实现” 一文中,我们将dto、controller归入了UI层。同时,在UI层中,我们还会去使用infrastructure层中的validation、logging、checkLogin等公共组件完成一些通用的动作。接下来我们将逐一讲解。

2. Controller

controller是公司前台

  这里的controller承担这一个请求受理的角色,就像是一个公司的前台,接受不同的请求(人员来访、电话咨询、快递/信件签收等等),必要时还会对不同的请求进行权限校验,以防坏人捣蛋(比如:查验来访者的身份,确认被访问者是否真有其人);并将不同格式的请求转换为通用的请求格式(用标准的邮件/电话/短信通知责任人);将请求转发到对应的负责人(可能是将电话转接给负责人,也可能是将应聘者介绍给面试官,还可能是叫某个程序猿出来取快递);到最后还会将来访者登录在案,必要时,还会通过前台告知具体的处理结果(告知电话咨询者其要求是否能得到满足,告知来访者他要找的人没有上班等)。

controller的职责

  类比与前台工作,我们可以发现controller有如下职责:

  •   接受请求;
  •        请求格式校验及转换;
  •   权限校验;
  •   路由请求;
  •   记录请求;
  •   回复响应;

controller的实现

  在示例代码中,我们使用spring-mvc框架实现,controller就直接使用spring-mvc中的controller层完成。对应到上述职责分别有如下组件完成:

  •   接受请求 —— Spring-MVC的DispatcherServlet组件完成;
  •        请求格式校验及转换 —— 格式校验遵循java Validation规范,使用Hibernate的validator组件完成;最终会被转换为DTO组件,并在DTO组件中落地validation,放到后面专门讲解;
  •   权限校验 —— 自实现的CheckLogin组件完成;放到后面专门讲解;
  •   路由请求 ——  Spring-MVC的@RequestMapping组件完成;
  •   记录请求 —— 自实现的Loggin组件完成;放到后面专门讲解;
  •   回复响应 —— Spring-MVC的@ResponseBody组件完成;

BaseController

  在实际编码中,发现所有的controller会有一些公共的行为,比如异常处理,封装响应dto,我们将这些行为抽象出来,放在BaseController中,所有的业务Controller继承这个基础类。

类图

 

 

 代码示例

 

复制代码

 1 public class BaseController {
 2 
 3     private static Logger logger = LogManager.getLogger(BaseController.class);
 4     
 5     @Autowired
 6     private ApplicationUtil applicationUtil;
 7     
 8     @Autowired
 9     private ExceptionHandler exceptionHandler;
10 
11     /**
12      * format 失败 response。
13      * @param e
14      * @return
15      */
16     protected ResponseDto formatErrorResponse(final Exception e) {
17         ResponseDto responseDto = new ResponseDto();
18         //将response 的data body置为空
19         responseDto.setBody(null);
20 
21         //依据异常类型进行分别处理,将异常信息转义为用户友好的提示信息
22         Map<String, String> exceptionMap = exceptionHandler.handle(e);
23         responseDto.setReturnCode(exceptionMap.get("errorCode"));
24         responseDto.setReturnMsg(exceptionMap.get("errorMsg"));
25         logger.debug("Response is: "+responseDto);
26         return responseDto;
27     }
28     
29     
30     /**
31      * format成功的response
32      * @param responseBody
33      * @return ResponseDto
34      */
35     protected ResponseDto formatSuccessResponse(ResponseBody responseBody) {
36         ResponseDto responseDto = new ResponseDto();
37         //设置返回码和返回信息为成功
38         responseDto.setReturnCode(ReturnCode.SUCCESS);
39         responseDto.setReturnMsg(applicationUtil.getReturnMsg(ReturnCode.SUCCESS));
40         
41         //将response 的data body置为实际的业务body
42         responseDto.setBody(responseBody);
43         logger.debug("Response is: "+responseDto);
44         return responseDto;
45     }
46     
47     
48 }

复制代码

复制代码

 1 @Controller
 2 @RequestMapping("/post")
 3 public class PostController extends BaseController {
 4     
 5     @Autowired
 6     private PostService postService;
 7 
 8     /**
 9      *  发布帖子
10      * @param requestDto
11      * @return  ResponseDto
12      */
13     @ResponseBody
14     @RequestMapping(value = "/posting", method = RequestMethod.POST)
15     public ResponseDto posting(@RequestBody @Valid RequestDto<PostingReqBody> requestDto) {
16         try {
17             PostingRespBody postingRespBody = postService.posting (requestDto);
18             return this.formatSuccessResponse(postingRespBody);
19         } catch (Exception e) {
20             return this.formatErrorResponse(e);
21         }
22     }
23     .......
24 }

复制代码

 

3.  DTO

DTO是controller和service之间数据传输的载体

  DTO(Data Transfer Object),顾名思义,DTO用于组件/分层间传递数据,它是数据传递的载体。在这里我们用它作为 “controller <=> service” 之间传递数据的载体。controller传递一个RequestDto给service,service完成业务处理后,返回一个ReponseDto。

类图

 

 

  请求中公共的属性(请求头)放置到RequestDto中。RequestDto持有一个RequestBody(请求体),包含各个场景的具体业务请求参数,所有的业务请求都会有自己的ReqBody,并继承至RequestBody。

  ResponseDto同理,不再赘述。

示例代码

复制代码

1 /**
2  * User Interface layer Data Transfer Object
3  * @author daoqidelv
4  * @createdate 2017年9月24日
5  */
6 public interface UIDto {
7 
8 }

复制代码

复制代码

 1 public class RequestDto<T> implements UIDto {
 2     
 3     /**
 4      * 请求渠道
 5      */
 6     private String channel;
 7     
 8     /**
 9      * 请求id
10      */
11     private String requestId;
12     
13     /**
14      * 对body使用validation
15      */
16     @Valid 
17     private T body;
18 
19     public String getChannel() {
20         return channel;
21     }
22 
23     public void setChannel(String channel) {
24         this.channel = channel;
25     }
26 
27     public String getRequestId() {
28         return requestId;
29     }
30 
31     public void setRequestId(String requestId) {
32         this.requestId = requestId;
33     }
34 
35 
36     public T getBody() {
37         return body;
38     }
39 
40     public void setBody(T body) {
41         this.body = body;
42     }
43     
44 }

复制代码

复制代码

 1 public class ResponseDto implements UIDto{
 2     
 3     /**
 4      * 状态码
 5      */
 6     private String returnCode;
 7 
 8     /**
 9      * 提示信息
10      */
11     private String returnMsg;
12 
13     /**
14      * 各个接口返回的数据
15      */
16     private Object body;
17 
18     public String getReturnCode() {
19         return returnCode;
20     }
21 
22     public void setReturnCode(String returnCode) {
23         this.returnCode = returnCode;
24     }
25 
26     public String getReturnMsg() {
27         return returnMsg;
28     }
29 
30     public void setReturnMsg(String returnMsg) {
31         this.returnMsg = returnMsg;
32     }
33 
34     public Object getBody() {
35         return body;
36     }
37 
38     public void setBody(Object body) {
39         this.body = body;
40     }
41     
42 }

复制代码

1 public class RequestBody {
2 
3 }

1 public class ResponseBody {
2 
3 }

复制代码

public class PostingReqBody extends RequestBody {

    @NotEmpty(message="{request.userId.not.empty}")
    private String userId;
    
    private String title;
    
    @NotEmpty(message="{request.post.posting.content.not.empty}")
    private String sourceContent;

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getSourceContent() {
        return sourceContent;
    }

    public void setSourceContent(String sourceContent) {
        this.sourceContent = sourceContent;
    }  

}

复制代码

复制代码

 1 public class PostingRespBody extends ResponseBody {
 2     
 3     private String postId;
 4 
 5     public String getPostId() {
 6         return postId;
 7     }
 8 
 9     public void setPostId(String postId) {
10         this.postId = postId;
11     }
12 }

复制代码

 

4.  infrastructure层的公共组件

 checkLogin

称职的门将

  checkLogin组件负责登录态校验,或者叫做会话控制,有时候还需要做权限校验,它是一个称职的门将。不同的部署架构对checkLogin的实现要求不一样。在传统的单体应用中,会话控制一般交给web容器来管理,通常使用filter统一管控会话,在分布式系统中,由于服务组件本身要求无会话状态,故会单独有一个SessionServer来负责会话控制。checkLogin针对上述两种不同的实现方案。

  传统的单体应用中个,直接使用filter实现,交给web容器来管理会话。对于分布式系统,则由checkLogin组件完成SessionServer的交互,接受sessionServer返回的会话校验结果,并结合当前请求的权限要求,做出相应的会话控制。

  本示例代码中未给出CheckLogin的具体实现,基本思路如上面所示。如果使用SpringMVC框架,可以使用AOP的方式,将CheckLogin以注解的方式实现,在需要进行会话控制的controller方法上,加上@CheckLogin注解。或者可以使用Spring 的HandlerInterceptor拦截器实现,可以让RequestBody实现类根据业务场景决定是否实现LoginAuthority接口,且可以根据不同的权限设置不同的LoginAuthority接口,然后在Interceptor统一做拦截处理。

类图

  采用Interceptor形式实现CheckLogin,可能的类图如下:

  UserLoginAuthority表示需要用户登录态,AccountLoginAuthority表示需要账户登录态,AccountLoginAuthority权限要求高于UserLoginAuthority。而PostingReqBody和DeletePostReqBody在所有登录态下都可以进行,因此只需实现LoginAuthority接口即可。

 

Logging

  Logging组件负责记录应用服务日子及关键操作日志,包括:api访问请求/响应内容、持久层访问记录、第三方服务调用记录等等。通常各个业务系统都有自己的日志组件和日志记录规范。所以本demo也不打算给出具体的代码实现。

  如果使用SpringMVC框架,可以尝试使用AOP的方式完成,可以做到对domain层无侵入,是比较友好的实践方式。

Validation

  Validation主要负责参数格式校验,确保进入到service层的参数是合法的,使入参对service更友好。

  JSR303 是一套JavaBean参数校验的标准,它定义了很多常用的校验注解。Hibernate validator 实现了JSR303g标准,并在标准基础上对校验注解进行了扩展,主要增加了@NotEmpty注解,这在demo中有使用到。

  如何集成Hibernate validator 可以参阅如下blog:Java Bean Validation 最佳实践

  或者参考demo代码,不再赘述。

ExceptionHandler

异常都交给UI层统一处理

  ExceptionHandler是自定义的异常处理器,主要负责对service抛出的所有异常进行拦截及处理,针对不同的异常类型,转义成不同的错误码和错误提示信息返回给调用方。这样做的好处在于:让UI层之外的所有层都不再去关注exception,全部由UI层hold住,同时完成异常的统一化处理,确保exception不会漏处理,提升服务的友好性。

  demo示例中,主要有如下几类异常,项目中可以根据具体情况做扩展:

  •   BusinessException —— domain层抛出的业务领域相关异常,比如:用户访问的帖子不存在;
  •   CommunicationException —— 通讯相关异常,出现此异常,表明系统出现了故障,需要做友好提示;往往是和第三方服务交互时,第三方服务不可用或者响应超时;
  •   OutsideServiceException —— 第三方服务返回的业务异常,注意是业务异常,区别于CommunicationException在于:请求响应成功,但是业务上没能成功;比如:调用用户体系服务组件查询用户信息,用户体系服务组件返回“查无此人”;
  •   DataAccessException —— 持久层返回的数据存取相关的异常,可能是程序bug或者系统故障,需要做友好提示。

类图

 

5. UI层类图

 

6. 代码示例  

  此demo的代码已上传至github,欢迎下载和讨论,但拒绝被用于任何商业用途。

  github地址:https://github.com/daoqidelv/community-ddd-demo/tree/master

  branch:master

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值