【DDD】领域驱动设计实践 —— Application层实现
本文是DDD框架实现讲解的第二篇,主要介绍了DDD的Application层的实现,详细讲解了service、assemble的职责和实现。文末附有github地址。相比于《领域驱动设计》原书中的航运系统例子,社交服务系统的业务场景对于大家更加熟悉,相信更好理解。本文是【DDD】系列文章的其中一篇,其他可参考:使用领域驱动设计思想实现业务系统
Application层
在DDD设计思想中,Application层主要职责为组装domain层各个组件及基础设施层的公共组件,完成具体的业务服务。Application层可以理解为粘合各个组件的胶水,使得零散的组件组合在一起提供完整的业务服务。在复杂的业务场景下,一个业务case通常需要多个domain实体参与进来,所以Application的粘合效用正好有了用武之地。
Application层主要由:service、assembler组成,下面分别对其做讲解。
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判定,这是不友好的。
类图

代码示例
@Service
public class PostServiceImpl implements PostService { @Autowired
private IPostRepository postRepository; @Autowired
private PostAssembler postAssembler; public PostingRespBody posting(RequestDto<PostingReqBody> requestDto) throws BusinessException {
PostingReqBody postingReqBody = requestDto.getBody();
/**
*NOTE: 请求参数校验交给了validation,这里无需校验userId和postId是否为空
*/
String userId = postingReqBody.getUserId();
String title = postingReqBody.getTitle();
String sourceContent = postingReqBody.getSourceContent(); long userIdInLong = Long.valueOf(userId); /**
* 组装domain model entity
* NOTE:这里的PostAuthor不需要从repository重载,原因在于:deletePost场景需要用户登录后才能操作,
* 在进入service之前,已经在controller层完成了用户身份鉴权,故到达这里的userId肯定是合法的用户
*/
PostAuthor postAuthor = new PostAuthor(userIdInLong);
Post post = postAuthor.posting(title, sourceContent); /**
* NOTE:使用repository将model entity 写入存储
*/
postRepository.save(post); /**
* NOTE:使用postAssembler将Post model组装成dto返回。
*/
return postAssembler.assemblePostingRespBody(post);
} public DeletePostRespBody delete(RequestDto<DeletePostReqBody> requestDto) throws BusinessException {
DeletePostReqBody deletePostReqBody = requestDto.getBody(); /**
*NOTE: 请求参数校验交给了validation,这里无需校验userId和postId是否为空
*/
String userId = deletePostReqBody.getUserId();
String postId = deletePostReqBody.getPostId(); long userIdInLong = Long.valueOf(userId);
long postIdInLong = Long.valueOf(postId); /**
* 组装domain model entity
* NOTE:这里的PostAuthor不需要从repository重载,原因在于:deletePost场景需要用户登录后才能操作,
* 在进入service之前,已经在controller层完成了用户身份鉴权,故到达这里的userId肯定是合法的用户
*/
PostAuthor postAuthor = new PostAuthor(userIdInLong);
/**
* 从repository中重载domain model entity
* 借此判断该postId是否真的存在帖子
*/
Post post = postRepository.query(postIdInLong); postAuthor.deletePost(post); postRepository.delete(post); return null;
} @Override
public QueryPostDetailRespBody queryPostDetail(RequestDto<QueryPostDetailReqBody> requestDto)
throws BusinessException {
QueryPostDetailReqBody queryPostDetailReqBody = requestDto.getBody(); String readerId = queryPostDetailReqBody.getReaderId();
String postId = queryPostDetailReqBody.getPostId(); long readerIdInLong = Long.valueOf(readerId);
long postIdInLong = Long.valueOf(postId); //TODO 可能有一些权限校验,比如:判定该读者是否有查看作者帖子的权限等。这里暂且不展开讨论。
PostReader postReader = new PostReader(readerIdInLong); Post post = postRepository.query(postIdInLong); /**
* NOTE: 使用postAssembler将domain层的model组装成dto,组装过程:
* 1、完成类型转换、数据格式化;
* 2、将多个model组合成一个dto,一并返回。
*/
return postAssembler.assembleQueryPostDetailRespBody(post);
} }
Assembler
Assembler是组装器
Assembler是组装器,负责完成domain model对象到dto的转换,组装职责包括:
- 完成类型转换、数据格式化;如日志格式化,状态enum装换为前端认识的string;
- 将多个domain领域对象组装为需要的dto对象,比如查询帖子列表,需要从Post(帖子)领域对象中获取帖子的详情,还需要从User(用户)领域对象中获取用户的社交信息(昵称、简介、头像等);
- 将domain领域对象属性裁剪并组装为dto;某些场景下,可能并不需要所有domain领域对象的属性,比如User领域对象的password属性属于隐私相关属性,在“查询用户信息”case中不需要返回,需要裁剪掉。
示例代码
/**
* Post模块的组装器,完成domain model对象到dto的转换,组装职责包括:
* 1、完成类型转换、数据格式化;如日志格式化,状态enum装换为前端认识的string;
* 2、将多个model组合成一个dto,一并返回。
* TODO: 不太好的地方每个assemble方法都需要先判断入参对象是否为空。
* @author daoqidelv
* @createdate 2017年9月24日
*/
@Component
public class PostAssembler { private final static String POSTING_TIME_STRING_DATE_FORMAT = "yyyy-MM-dd hh:mm:ss"; @Autowired
private ApplicationUtil applicationUtil; public PostingRespBody assemblePostingRespBody(Post post) {
if(post == null) {
return null;
}
PostingRespBody postingRespBody = new PostingRespBody();
postingRespBody.setPostId(String.valueOf(post.getId()));
return postingRespBody;
} public QueryPostDetailRespBody assembleQueryPostDetailRespBody(Post post) {
/**
* NOTE: 判定入参post是否为null
*/
if(post == null) {
return null;
}
QueryPostDetailRespBody queryPostDetailRespBody = new QueryPostDetailRespBody();
queryPostDetailRespBody.setAuthorId(String.valueOf(post.getAuthorId())); //完成类型转换
queryPostDetailRespBody.setPostId(String.valueOf(post.getId()));//完成类型转换
queryPostDetailRespBody.setPostingTime(
applicationUtil.convertTimestampToString(post.getPostingTime(), POSTING_TIME_STRING_DATE_FORMAT));//完成日期格式化
queryPostDetailRespBody.setSourceContent(post.getSourceContent());
queryPostDetailRespBody.setTitle(post.getTitle());
return queryPostDetailRespBody;
} }
思考
上述代码实现中,每一个assemble方法都需要校验入参对象是否为空,实践中发现,这一个关键点很容易遗漏,没有想到好的办法解决。
类图

demo
此demo的代码已上传至github,欢迎下载和讨论,但拒绝被用于任何商业用途。
github地址:https://github.com/daoqidelv/community-ddd-demo/tree/master
branch:master
【DDD】领域驱动设计实践 —— Application层实现的更多相关文章
- 【DDD】领域驱动设计实践 —— UI层实现
前面几篇blog主要介绍了DDD落地架构及业务建模战术,后续几篇blog会在此基础上,讲解具体的架构实现,通过完整代码demo的形式,更好地将DDD的落地方案呈现出来.本文是架构实现讲解的第一篇,主要 ...
- DDD领域驱动设计之运用层代码
1.DDD领域驱动设计实践篇之如何提取模型 2.DDD领域驱动设计之聚合.实体.值对象 3.DDD领域驱动设计之领域基础设施层 4.DDD领域驱动设计之领域服务 5.整体DEMO代码 什么是运用层,说 ...
- 【DDD】领域驱动设计实践 —— Domain层实现
本文是DDD框架实现讲解的第三篇,主要介绍了DDD的Domain层的实现,详细讲解了entity.value object.domain event.domain service的职责,以及如何识别出 ...
- DDD领域驱动设计和实践(转载)
-->目录导航 一. DDD领域驱动设计介绍 1. 什么是领域驱动设计(DDD) 2. 领域驱动设计的特点 3. 如果不使用DDD? 4. 领域驱动设计的分层架构和构成要素 5. 事务脚本和领域 ...
- DDD 领域驱动设计-谈谈 Repository、IUnitOfWork 和 IDbContext 的实践(2)
上一篇:<DDD 领域驱动设计-谈谈 Repository.IUnitOfWork 和 IDbContext 的实践(1)> 阅读目录: 抽离 IRepository 并改造 Reposi ...
- DDD 领域驱动设计-谈谈 Repository、IUnitOfWork 和 IDbContext 的实践(1)
好久没写 DDD 领域驱动设计相关的文章了,嘎嘎!!! 这几天在开发一个新的项目,虽然不是基于领域驱动设计的,但我想把 DDD 架构设计的一些东西运用在上面,但发现了很多问题,这些在之前的短消息项目中 ...
- DDD 领域驱动设计-谈谈 Repository、IUnitOfWork 和 IDbContext 的实践(转)
http://www.cnblogs.com/xishuai/p/ddd-repository-iunitofwork-and-idbcontext.html 好久没写 DDD 领域驱动设计相关的文章 ...
- DDD领域驱动设计落地实践(十分钟看完,半小时落地)
一.引子 不知今年吹了什么风,忽然DDD领域驱动设计进入大家视野.该思想源于2003年 Eric Evans编写的"Domain-Driven Design领域驱动设计"简称DDD ...
- DDD 领域驱动设计-谈谈 Repository、IUnitOfWork 和 IDbContext 的实践(3)
上一篇:<DDD 领域驱动设计-谈谈 Repository.IUnitOfWork 和 IDbContext 的实践(2)> 这篇文章主要是对 DDD.Sample 框架增加 Transa ...
随机推荐
- 数据库-MYSQL安装配置和删除
* 课程回顾: * 完成注册和登陆的功能. * 准备的工作 * 技术.开源jar包 * 开发的功能使用MVC模式 * C:控制层(接收请求和从客户端发送过来的参数) * 接收参数(request对象) ...
- 利用js实现禁用浏览器后退
原博主链接为:http://blog.csdn.net/zc474235918/article/details/53138553 现在很多的内部系统,一些界面,都是用户手动点击退出按钮的.但是为了避免 ...
- Nginx学习之HTTP/2.0配置
哎呀,一不小心自己的博客也是HTTP/2.0了,前段时间对网站进行了https迁移并上了CDN,最终的结果是这酱紫的(重点小绿锁,安全标示以及HTTP/2.0请求). 科普 随着互联网的快速发展,HT ...
- 编写JsonResult封装JSON返回值(模板参阅)
编写JsonResult封装JSON返回值 package cn.tedu.note.util; import java.io.Serializable; import cn.tedu.note.se ...
- Laravel学习基础篇之--路由
终于还是决定再多学一门重量级框架,当然首选必备还是被称为最优雅的Web开发框架--Laravel 对于框架的入门,首先了解它的路由规则是先前必备的,以下是laravel 中几种常见的基础路由规则 // ...
- System.Globalization.CultureInfo.InvariantCulture 解决不同地域字符串格式不同问题
同样的DateTime.ToShortDateString() 在不同的地域输出格式不同 如在美国的 日期格式为 : 月-日-年 如在中国的 日期格式为 : 年-月-日 一些时候,这个格式就会 ...
- Mac系统的终端显示git当前分支
当我第一次在mac系统下使用git的时候,发现一个问题,git默认是不显示当前所在的分支名称,然后网上查找资料,找到了解决办法,终于可以显示本地当前分支,现在分享如下. 1 进入你的home目录 cd ...
- 201521123084 《Java程序设计》第13周学习总结
1. 本周学习总结 以你喜欢的方式(思维导图.OneNote或其他)归纳总结多网络相关内容. answer: (1)netassist可以用来链接IP端口 (2)accept方法可以用来监听端口,当没 ...
- A+B Format
1001.A+B Format (20) github上的object-roiented链接 https://github.com/a877306134/object-oriented/tree/ma ...
- 团队作业8----第二次项目冲刺(Beta阶段) 第四天
BETA阶段冲刺第四天 1.小会议ing 2.每个人的工作 (1)昨天已完成的工作 1.修改了学生上传的方式: 2.完善了学生和老师修改的代码: (2) 今天计划完成的工作 (3) 工作中遇到的困难: ...