【DDD】领域驱动设计实践 —— UI层实现
前面几篇blog主要介绍了DDD落地架构及业务建模战术,后续几篇blog会在此基础上,讲解具体的架构实现,通过完整代码demo的形式,更好地将DDD的落地方案呈现出来。本文是架构实现讲解的第一篇,主要介绍了DDD的User Interface层的实现,详细讲解了controller、dto的职责和实现,已经UI层使用到的公共组件:CheckLogin、Loging、Validation的职责和实现细节。文末附有github地址。相比于《领域驱动设计》原书中的航运系统例子,社交服务系统的业务场景对于大家更加熟悉,相信更好理解。本文是【DDD】系列文章的其中一篇,其他可参考:使用领域驱动设计思想实现业务系统
User Interface
User Interface层是用户接口层,为用户/调用方提供可访问的接口,我们简称为“UI”层。在 “【DDD】领域驱动设计实践 —— 框架实现” 一文中,我们将dto、controller归入了UI层。同时,在UI层中,我们还会去使用infrastructure层中的validation、logging、checkLogin等公共组件完成一些通用的动作。接下来我们将逐一讲解。
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 }
BaseController.java
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 }
PostController.java
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 }
UIDto.java
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 }
RequestDto.java
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 }
ResponseDto
1 public class RequestBody { 2 3 }
RequestBody.java
1 public class ResponseBody { 2 3 }
ResponseBody.java
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;} }
PostingReqBody.java
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 }
PostingRespBody.java
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或者系统故障,需要做友好提示。
类图
UI层类图
代码示例
此demo的代码已上传至github,欢迎下载和讨论,但拒绝被用于任何商业用途。
github地址:https://github.com/daoqidelv/community-ddd-demo/tree/master
branch:master
转载于:https://www.cnblogs.com/daoqidelv/p/7587478.html
【DDD】领域驱动设计实践 —— UI层实现相关推荐
- DDD 领域驱动设计落地实践:六步拆解 DDD
引言 相信通过前面几篇文章的介绍,大家对于 DDD 的相关理论以及实践的套路有了一定的理解,但是理解 DDD 理论和实践手段是一回事,能不能把这些理论知识实际应用到我们实际工作中又是另外一回事,因此本 ...
- 【DDD落地实践系列】DDD 领域驱动设计落地实践:六步拆解 DDD
引言 相信通过前面几篇文章的介绍,大家对于 DDD 的相关理论以及实践的套路有了一定的理解,但是理解 DDD 理论和实践手段是一回事,能不能把这些理论知识实际应用到我们实际工作中又是另外一回事,因此本 ...
- DDD 领域驱动设计落地实践系列:工程结构分层设计
引言 前面几篇文章中,笔者给大家阐述了 DDD 领域驱动设计的三大过程,重点围绕如何通过战略设计与战术设计进行 DDD 落地实践进行了详细的讨论,但是还没有涉及到工程层面的落地.实际上所有的这些架构理 ...
- DDD领域驱动设计落地实践系列:战略设计和战术设计
引言 通过前面的文章介绍,相信大家对于什么是DDD有了初步的了解,知道它是一种微服务的架构设计方法论,为我们解决如何建立领域模型,如何实现微服务划分等提供了方向和指导.但是对于如何具体落地使用DDD, ...
- DDD 领域驱动设计落地实践系列:战略设计和战术设计
引言 通过前面的文章介绍,相信大家对于什么是 DDD 有了初步的了解,知道它是一种微服务的架构设计方法论,为我们解决如何建立领域模型,如何实现微服务划分等问题提供了方向和指导.但是对于如何具体落地使用 ...
- DDD领域驱动设计之领域基础设施层
1.DDD领域驱动设计实践篇之如何提取模型 2.DDD领域驱动设计之聚合.实体.值对象 其实这里说的基础设施层只是领域层的一些接口和基类而已,没有其他的如日子工具等代码,仅仅是为了说明领域层的一些基础 ...
- EntityFramework之领域驱动设计实践(十)(转)
http://www.cnblogs.com/daxnet/archive/2010/07/19/1780764.html 规约(Specification)模式 本来针对规约模式的讨论,我并没有想将 ...
- C#进阶系列——DDD领域驱动设计初探(五):AutoMapper使用
前言:前篇搭建了下WCF的代码,就提到了DTO的概念,对于为什么要有这么一个DTO的对象,上章可能对于这点不太详尽,在此不厌其烦再来提提它的作用: 从安全上面考虑,领域Model都带有领域业务,让Cl ...
- [转]浅析DDD(领域驱动设计)
最近在做一些微服务相关的设计,内容包括服务的划分,Restful API的设计等.其中比较棘手的就是Service的职责划分:如何抽象具有统一业务范畴的Model,使其模块化,又如何高度提炼并组合多模 ...
最新文章
- VS.NET 学习方法论
- HttpClient 如何设置请求接口等待时间
- 中美首份8000字长文解析全球热点脑机接口(重磅干货)
- php如何获取select multiple的值
- 49 款人脸检测/识别的API、库和软件
- 福州大学2013java期末试卷_2020-11-04:java里,总体说一下集合框架。
- BP反向传播算法浅谈(Error Back-propagation)
- 开源组件ExcelReport 3.x.x 使用手册(为.netcore而来
- android 监听布局改变,Android通过监听最外层布局的改变监听键盘的状态,软键盘的弹出和收起都会改变外层布局(前提是把Activity的mode设置成压缩);...
- [WPF]ListView点击列头排序功能实现
- python线程等待_python3 中 Event.wait 多线程等待
- 即插即用 | 超越CBAM,全新注意力机制,GAM不计成本提高精度(附Pytorch实现)...
- 阿里云服务器部署GeoServer以及跨域处理
- 几个分形的matlab实现1,基于MATLAB实现分形图形的绘制.doc
- win7安装IIS后如何远程访问IIS
- 坚果pro3刷miui_锤子科技坚果Pro 3(12GB/256GB/全网通)手机卡怎么办?
- MFI认证——苹果MFI账号申请
- 论文研读笔记(二)——VGG
- 容器-Docker《二》命令帮助镜像管理
- 【算法】图解A* 搜索算法
热门文章
- Python 语言 SAP2000 二次开发 实例
- 我在百度Python小白逆袭大神课程中“打怪”欢乐之旅
- 梦幻摩天轮——用CSS写一个元素发光以及圆盘旋转的效果
- 中国石油大学《安全管理学》第三阶段在线作业
- Excel下的数据挖掘:学生成绩统计分析实战之排名
- TensorFlow 模型保存/载入的两种方法
- 极云普惠云电脑 v1.2.9
- 数据完全存于内存的数据集类
- 智慧城市物联网收入将在2026年达620亿美元
- 学生DW静态网页设计——代码质量好-家乡广州 (5页) HTML+CSS+JavaScript web网页制作期末大作业