【DDD 8】领域驱动设计实践 —— Application层实现
【DDD】领域驱动设计实践 —— Application层实现
目录
【DDD】领域驱动设计实践 —— Application层实现
1. Application层
2. Service
service是组件粘合剂
思考
类图
代码示例
3. Assembler
Assembler是组装器
示例代码
思考
4. 类图
5. demo
正文
本文是DDD框架实现讲解的第二篇,主要介绍了DDD的Application层的实现,详细讲解了service、assemble的职责和实现。文末附有github地址。相比于《领域驱动设计》原书中的航运系统例子,社交服务系统的业务场景对于大家更加熟悉,相信更好理解。本文是【DDD】系列文章的其中一篇,其他可参考:使用领域驱动设计思想实现业务系统
1. Application层
在DDD设计思想中,Application层主要职责为组装domain层各个组件及基础设施层的公共组件,完成具体的业务服务。Application层可以理解为粘合各个组件的胶水,使得零散的组件组合在一起提供完整的业务服务。在复杂的业务场景下,一个业务case通常需要多个domain实体参与进来,所以Application的粘合效用正好有了用武之地。
Application层主要由:service、assembler组成,下面分别对其做讲解。
2. Service
service是组件粘合剂
这里的Service区别于domain层的domain service,是应用服务。它是组件的粘合剂,组合domain层的各个组件和 infrastructure层的持久化组件、消息组件等等,完成具体的业务逻辑,提供完整的业务服务。
通过不断的实践,我们发现:通过DDD实现业务服务时,检验业务模型的质量的一个标准便是 —— service方法中不要有if/else。如果存在if/else,要么就是系统用例存在耦合,要么就是业务模型不够友好,导致部分业务逻辑泄漏到service了。
通常意义上,一个业务case在service层便会对应一个service方法,这样确保case实现的独立性。拿社区服务中的“帖子”模块来讲,我们有如下几个明显的case:发帖(posting)、删帖(deletePost)、查询帖子详情(queryPostDetail),这些case在service层都对应独立的业务方法。
思考
对于较为复杂的case:查询帖子列表,可能需要根据不同的tag过滤帖子,或者查询不同类型的帖子,或者查询热门帖子,这个时候应当用一个service方法实现呢?还是多个呢?
考虑这个问题,主要从这两方面入手:domain的一致性,数据存储的一致性;如果两个一致性都满足,那么我们可以在一个业务方法中完成,否则就要在独立的业务方法中完成。
例如:根据帖子运营标签查询帖子 和 查询全部帖子列表 这两个case我们可以放到一个service方法中实现,因为前一个case只是在后一个case的基础上加了一个过滤条件,这个过滤条件完全可以交给dao层的sql where条件处理掉,除此之外,domain和repository都完全一样;
而“查询热门帖子” 这个case就不能和上面的两个case共用一个service方法了,因为热门帖子列表的数据源并不在数据库中,而是存在于缓存中,因此repository的取数逻辑存在很大差异,如果共用一个service方法,势必要在service层出现if/else判定,这是不友好的。
类图
代码示例
1 @Service2 public class PostServiceImpl implements PostService {3 4 @Autowired5 private IPostRepository postRepository;6 7 @Autowired8 private PostAssembler postAssembler;9 10 11 12 public PostingRespBody posting(RequestDto<PostingReqBody> requestDto) throws BusinessException { 13 PostingReqBody postingReqBody = requestDto.getBody(); 14 /** 15 *NOTE: 请求参数校验交给了validation,这里无需校验userId和postId是否为空 16 */ 17 String userId = postingReqBody.getUserId(); 18 String title = postingReqBody.getTitle(); 19 String sourceContent = postingReqBody.getSourceContent(); 20 21 long userIdInLong = Long.valueOf(userId); 22 23 /** 24 * 组装domain model entity 25 * NOTE:这里的PostAuthor不需要从repository重载,原因在于:deletePost场景需要用户登录后才能操作, 26 * 在进入service之前,已经在controller层完成了用户身份鉴权,故到达这里的userId肯定是合法的用户 27 */ 28 PostAuthor postAuthor = new PostAuthor(userIdInLong); 29 Post post = postAuthor.posting(title, sourceContent); 30 31 /** 32 * NOTE:使用repository将model entity 写入存储 33 */ 34 postRepository.save(post); 35 36 /** 37 * NOTE:使用postAssembler将Post model组装成dto返回。 38 */ 39 return postAssembler.assemblePostingRespBody(post); 40 } 41 42 43 public DeletePostRespBody delete(RequestDto<DeletePostReqBody> requestDto) throws BusinessException { 44 DeletePostReqBody deletePostReqBody = requestDto.getBody(); 45 46 /** 47 *NOTE: 请求参数校验交给了validation,这里无需校验userId和postId是否为空 48 */ 49 String userId = deletePostReqBody.getUserId(); 50 String postId = deletePostReqBody.getPostId(); 51 52 long userIdInLong = Long.valueOf(userId); 53 long postIdInLong = Long.valueOf(postId); 54 55 /** 56 * 组装domain model entity 57 * NOTE:这里的PostAuthor不需要从repository重载,原因在于:deletePost场景需要用户登录后才能操作, 58 * 在进入service之前,已经在controller层完成了用户身份鉴权,故到达这里的userId肯定是合法的用户 59 */ 60 PostAuthor postAuthor = new PostAuthor(userIdInLong); 61 /** 62 * 从repository中重载domain model entity 63 * 借此判断该postId是否真的存在帖子 64 */ 65 Post post = postRepository.query(postIdInLong); 66 67 postAuthor.deletePost(post); 68 69 postRepository.delete(post); 70 71 return null; 72 } 73 74 75 @Override 76 public QueryPostDetailRespBody queryPostDetail(RequestDto<QueryPostDetailReqBody> requestDto) 77 throws BusinessException { 78 QueryPostDetailReqBody queryPostDetailReqBody = requestDto.getBody(); 79 80 String readerId = queryPostDetailReqBody.getReaderId(); 81 String postId = queryPostDetailReqBody.getPostId(); 82 83 long readerIdInLong = Long.valueOf(readerId); 84 long postIdInLong = Long.valueOf(postId); 85 86 //TODO 可能有一些权限校验,比如:判定该读者是否有查看作者帖子的权限等。这里暂且不展开讨论。 87 PostReader postReader = new PostReader(readerIdInLong); 88 89 Post post = postRepository.query(postIdInLong); 90 91 /** 92 * NOTE: 使用postAssembler将domain层的model组装成dto,组装过程: 93 * 1、完成类型转换、数据格式化; 94 * 2、将多个model组合成一个dto,一并返回。 95 */ 96 return postAssembler.assembleQueryPostDetailRespBody(post); 97 } 98 99 }
3. Assembler
Assembler是组装器
Assembler是组装器,负责完成domain model对象到dto的转换,组装职责包括:
- 完成类型转换、数据格式化;如日志格式化,状态enum装换为前端认识的string;
- 将多个domain领域对象组装为需要的dto对象,比如查询帖子列表,需要从Post(帖子)领域对象中获取帖子的详情,还需要从User(用户)领域对象中获取用户的社交信息(昵称、简介、头像等);
- 将domain领域对象属性裁剪并组装为dto;某些场景下,可能并不需要所有domain领域对象的属性,比如User领域对象的password属性属于隐私相关属性,在“查询用户信息”case中不需要返回,需要裁剪掉。
示例代码
1 /**2 * Post模块的组装器,完成domain model对象到dto的转换,组装职责包括:3 * 1、完成类型转换、数据格式化;如日志格式化,状态enum装换为前端认识的string;4 * 2、将多个model组合成一个dto,一并返回。5 * TODO: 不太好的地方每个assemble方法都需要先判断入参对象是否为空。6 * @author daoqidelv7 * @createdate 2017年9月24日8 */9 @Component 10 public class PostAssembler { 11 12 private final static String POSTING_TIME_STRING_DATE_FORMAT = "yyyy-MM-dd hh:mm:ss"; 13 14 @Autowired 15 private ApplicationUtil applicationUtil; 16 17 public PostingRespBody assemblePostingRespBody(Post post) { 18 if(post == null) { 19 return null; 20 } 21 PostingRespBody postingRespBody = new PostingRespBody(); 22 postingRespBody.setPostId(String.valueOf(post.getId())); 23 return postingRespBody; 24 } 25 26 public QueryPostDetailRespBody assembleQueryPostDetailRespBody(Post post) { 27 /** 28 * NOTE: 判定入参post是否为null 29 */ 30 if(post == null) { 31 return null; 32 } 33 QueryPostDetailRespBody queryPostDetailRespBody = new QueryPostDetailRespBody(); 34 queryPostDetailRespBody.setAuthorId(String.valueOf(post.getAuthorId())); //完成类型转换 35 queryPostDetailRespBody.setPostId(String.valueOf(post.getId()));//完成类型转换 36 queryPostDetailRespBody.setPostingTime( 37 applicationUtil.convertTimestampToString(post.getPostingTime(), POSTING_TIME_STRING_DATE_FORMAT));//完成日期格式化 38 queryPostDetailRespBody.setSourceContent(post.getSourceContent()); 39 queryPostDetailRespBody.setTitle(post.getTitle()); 40 return queryPostDetailRespBody; 41 } 42 43 }
思考
上述代码实现中,每一个assemble方法都需要校验入参对象是否为空,实践中发现,这一个关键点很容易遗漏,没有想到好的办法解决。
4. 类图
5. demo
此demo的代码已上传至github,欢迎下载和讨论,但拒绝被用于任何商业用途。
github地址:https://github.com/daoqidelv/community-ddd-demo/tree/master
branch:master
【DDD 8】领域驱动设计实践 —— Application层实现相关推荐
- 【DDD】领域驱动设计实践 —— UI层实现
前面几篇blog主要介绍了DDD落地架构及业务建模战术,后续几篇blog会在此基础上,讲解具体的架构实现,通过完整代码demo的形式,更好地将DDD的落地方案呈现出来.本文是架构实现讲解的第一篇,主要 ...
- DDD(领域驱动设计)系列主题:领域驱动设计(DDD)实践
过去几年,通天塔一直处于快速的业务能力建设和架构完善的阶段,以应对不断增长的业务需求和容量.高可用等技术需求,现在通天塔平台已经能满足集团主站的大部分活动.频道搭建和运营能力,主流程的新需求越来越少, ...
- 苏宁金融会员领域驱动设计实践
苏宁金融会员领域驱动设计实践 背景介绍 近年来,苏宁集团业务不断扩大,用户快速增长,线上线下融合不断深入,系统的复杂性越来越高,技术的广度和深度都在不断拓展. 在整个集团技术不断迭代演进的过程中,集团 ...
- EntityFramework之领域驱动设计实践(十)(转)
http://www.cnblogs.com/daxnet/archive/2010/07/19/1780764.html 规约(Specification)模式 本来针对规约模式的讨论,我并没有想将 ...
- DDD(领域驱动设计)分层架构
一.分层架构的模型 DDD全称为(Domain-Driven Design,简称DDD),领域驱动设计. 主要分为四层: 展现层:它负责向用户显示信息和解释用户命令,完成前端界面逻辑.这里的用户不一定 ...
- 什么是DDD(领域驱动设计)? 这是我见过最容易理解的一篇关于DDD 的文章了
领域驱动设计之领域模型 加一个导航,关于如何设计聚合的详细思考,见这篇文章. 2004年Eric Evans 发表Domain-Driven Design –Tackling Complexity i ...
- DDD 洋葱架构才是 yyds!阿里大牛手记(DDD)领域驱动设计应对之道
虽然身为架构师,设计一个高质量的架构依然是复杂与困难的. 简单来说,动用大量的资源只为了一套优质的三高架构并不正确,而是该在了解当前业务现状的情况下,创造出灵活.可维护.健硕能成长的. 就拿近两年程序 ...
- DDD洋葱架构才是 yyds,阿里架构师手记(DDD)领域驱动设计应对之道
虽然身为架构师,设计一个高质量的架构依然是复杂与困难的. 简单来说,动用大量的资源只为了一套优质的三高架构并不正确,而是该在了解当前业务现状的情况下,创造出灵活.可维护.健硕能成长的. 就拿近两年程序 ...
- 【你问我答】DDD(领域驱动设计)实践中遇到问题了?尽管问,我们负责解答!...
点击上方"蓝字"可以订阅哦 [你问我答]是由美团点评技术团队推出的线上问答服务,你在工作学习中遇到的各种技术问题,都可以通过我们微信公众号发问,我们6000+工程师会义务为你解答, ...
- DDD(领域驱动设计)
基本概念: 领域驱动设计(简称 ddd)概念来源于2004年著名建模专家eric evans发表的他最具影响力的书籍:<domain-driven design –tackling comple ...
最新文章
- 零基础学python全彩版实战答案-零基础学Python(全彩版)
- LA2678最短子序列
- Servlet入门篇(GenericServlet 类 - HttpServlet 类 -ServletConfig 接口 - HttpServletRequest 接口……)
- sql 统计记录条数后 打印出所有记录_用SQL完成购买行为分析(下篇II)
- 【DP】字串距离(luogu 1279)
- Unity之读取配置表去加载物体
- 【编程小题目6】字符数统计
- java爬虫(二)- Jsoup
- 计算机怎么查文件打印记录表,打印机打印文件历史记录如何查看
- 绪论(数据结构-邓俊辉)
- 数模电路基础知识 —— 5. 常见电路符号说明(三极管)
- 战神引擎传奇开服教程开服版本 开服服务器推荐战神引擎开服
- 2022 IDEA全家桶使用最新主题(免申请)
- 级数收敛与交换运算顺序
- 1.RecyclerView设置clipToPadding=“false“,scrollbars无法跟随列表滚动到底部的解决方案
- chrome 谷歌浏览器模拟各种手机设置userAgent
- 四种不同单源最短路径算法性能比较
- 中国味精市场销售现状与十四五发展趋势分析报告2022-2028
- Autodesk Inventor Routed Systems: Harness Autodesk Inventor Routed Systems: Harness Lynda课程中文字幕
- python HMAC SHA256 加密(python3 HmacSHA256加密)
热门文章
- oracle事务处理语言,Oracle DTL 数据事务语言
- 一种串口扩展电路应用
- ROS学习之CMakelists.txt和package.xml
- Windows下搭建wnmp
- Apache Flink 漫谈系列(12) - Time Interval(Time-windowed) JOIN
- STM32 I2S学习(一)
- ecshop 添加手机号码验证唯一性(手机版)
- 超低功耗MCU如何降低功耗
- python atan_Python atan或atan2,我应该使用什么?
- git版本管理软件——git储藏