欢迎大家关注公众号「JAVA前线」查看更多精彩分享文章,主要包括源码分析、实际应用、架构思维、职场分享、产品思考等等,同时欢迎大家加我个人微信「java_front」一起交流学习

1 前文回顾

我在之前文章《结合DDD讲清楚编写技术方案七大维度》介绍了从零到一使用DDD方法论搭建项目的七个步骤:

  • 四色分领域
  • 用例看功能
  • 流程三剑客
  • 领域与数据
  • 纵横做设计
  • 分层看架构
  • 接口看对接

四色分领域介绍了使用四色分析法将一个整体需求拆分为不同领域,这是DDD方法论核心思想。四色分析法同样可以用在子域或者限界上下文中,直到拆分出可以得心应手处理之边界为止。

用例看功能介绍了当领域划分完成之后,使用用例图描述系统功能。用例图不关心实现细节,而是从外部视角描述系统功能,即使不了解实现细节的人,通过用例图也可以快速了解系统功能。

流程三剑客介绍了使用活动图、顺序图、状态机图三种流程类型的图示描述系统,三种图各有特点:活动图着重描述逻辑分支,顺序图着重描述时间线索,状态机图着重描述状态流转。

领域与数据介绍了如何区分领域模型和数据模型。二者重要区别是值对象存储方式。领域模型在包含值对象的同时也保留了值对象的业务含义,而数据模型可以使用更加松散的结构保存值对象,简化数据库设计。

纵横做设计介绍了纵向做隔离,横向做编排。复杂业务之所以复杂,一个重要原因是涉及角色或者类型较多,很难平铺直叙地进行设计,所以我们需要增加分析维度。其中最常见的是增加横向和纵向两个维度。

分层看架构介绍了系统架构分为两个层次,第一种层次指本项目在整个公司位于哪一层次。持久层、缓存层、中间件、业务中台、服务层、网关层、客户端和代理层是常见的分层架构。第二种层次指本项目内部代码组织方式,一般可以分为接口层,访问层,业务层,领域层,整合层和基础层。

接口看对接介绍了一个接口代码编写完成后,这个接口如何调用,输入和输出参数是什么,这些问题需要在接口文档中得到回答。

本文沿用上文中足球运动员管理系统,主要从两个维度对上文进行扩充,第一个维度是将DDD中一些概念与上文进行映射,例如领域、子域、限界上下文、实体、值对象、聚合与领域事件。第二个维度是展示DDD项目结构层次。

2 领域、子域与限界上下文

2.1 核心概念

这三个词虽然不同但是实际上都是在描述【范围】这个概念。正如牛顿三定律有其适用范围,程序中变量有其作用域一样,DDD方法论也会将整体业务拆分成不同范围,在同一个范围内进行分析和处理。

上文实例中领域是足球,子域包括合同、医疗、训练、比赛、采访,合同子域可以分为两个限界上下文:转会和签约,医疗子域可以分为两个限界上下文:体检和伤病。

领域可以划分子领域,子域可以再划分子子域,限界上下文本质上是一种子子域,那么在业务分解时一个业务模块到底是领域、子域还是限界上下文?

这取决于看待这个模块的角度。你认为整体可能是别人的局部,你认为的局部可能是别人的整体,叫什么名字不重要,最重要的是按照高内聚原则将业务高度相关的模块收敛。

2.2 限界上下文

限界上下文(Bounded contenxt)比较难理解,我们可以四个维度分析:

第一个维度是限界上下文本身含义。限界表示了规定一个边界,上下文表示在这个边界内使用相同语义对象。例如goods这个词,在商品边界内被称为商品,但是快递边界内被称为货物。

第二个维度是子域与限界上下文关系。子域可以对应一个,也可以对应多个限界上下文。如果子域划分足够小,那么就是限界上下文。如果子域可以再细分,那么可以划分多个限界上下文。

第三维度是服务如何划分。子域和限界上下文都可以作为微服务,这里微服务是指独立部署的程序进程,具体拆分到什么维度是根据业务需要、开发资源、维护成本、技术实力等因素综合考量。如果按照子域进行微服务划分可以拆分为:

  • 基础服务:player-core-service
  • 合同服务:contract-core-service
  • 医疗服务:medical-core-service
  • 训练服务:training-core-service
  • 比赛服务:game-core-service
  • 采访服务:interview-core-service

如果按照限界上下文进行微服务划分,合同和医疗服务可以再拆分:

  • 基础合同服务:contract-base-service
  • 转会合同服务:contract-transfer-service
  • 签约合同服务:contract-signing-service
  • 基础医疗服务:medical-base-service
  • 伤病医疗服务:medical-injury-service
  • 体检医疗服务:medical-exam-service

第四个维度是交互维度。在同一个限界上下文中实体对象和值对象可以自由交流,在不同限界上下文中必须通过聚合根进行交流。聚合根可以理解为一个按照业务聚合的代理对象,例如产品经理作为需求收口人,任何需求应该先提给产品经理,通过产品经理整合后再提给程序员,而不是直接提给开发人员。

3 实体、值对象与聚合

领域模型分为三类:实体、值对象和聚合。

实体是具有唯一标识的对象,唯一标识会伴随实体对象整个生命周期并且不可变更。值对象本质上是属性的集合,没有唯一标识。

聚合包括聚合根和聚合边界两个概念,聚合根可以理解为一个按照业务聚合的代理对象,一个限界上下文企图访问另一个限界上下文内部对象,必须通过聚合根进行访问。

3.1 数据维度

领域模型与数据模型一个重要的区别是值对象存储方式。领域对象在包含值对象的同时也保留了值对象的业务含义,而数据对象可以使用更加松散的结构保存值对象,简化数据库设计。

如果需要管理足球运动员基本信息和比赛数据,对应领域模型和数据模型应该如何设计?姓名、身高、体重是一名运动员本质属性,加上唯一编号可以对应实体对象。跑动距离,传球成功率,进球数是运动员比赛表现,这些属性的集合可以对应值对象。

3.2 代码维度

3.2.1 数据对象

PO(Persistent Object)直接与数据库交互:

public class FootballPlayerPO {// 运动员IDprivate Long id;// 运动员姓名private String name;// 运动员身高private Integer height;// 运动员体重private Integer weight;// 比赛表现(JSON)private String gamePerformance;// 创建人private String creator;// 修改人private String updator;// 创建时间private Date createTime;// 修改时间private Date updateTime;
}

3.2.2 值对象

VO(Value Object)本质上是属性集合,没有唯一标识:

public class GamePerformanceVO {// 跑动距离private Double runDistance;// 传球成功率private Double passSuccess;// 进球数private Integer scoreNum;
}public class MaintainVO {// 创建人private String creator;// 修改人private String updator;// 创建时间private Date createTime;// 修改时间private Date updateTime;
}

3.2.3 实体对象

Entity具有唯一标识且会伴随实体对象整个生命周期:

public class FootballPlayerEntity {// 运动员IDprivate Long id;// 运动员姓名private String name;// 运动员身高private Integer height;// 运动员体重private Integer weight;// 比赛表现值对象private GamePerformanceVO gamePerformanceVO;
}

3.2.4 聚合对象

Agg(Aggregate)可以理解为一个按照业务聚合的代理对象,任何访问本限界上下文对象必须经过聚合。实践上可以理解为充血模型版本BO,在聚合对象中可以编写业务逻辑:

public class FootballPlayerSimpleResultAgg {// 运动员IDprivate Long playerId;// 运动员姓名private String playerName;
}public class FootballPlayerReadAgg implements BizValidator {// 运动员IDprivate Long playerId;// 页数private Integer pageNum;// 条数private Integer size;@Overridepublic void validate() {AssertUtil.notNull(playerId, new BizError);AssertUtil.notBigger(size, 100, new BizError);}
}public class FootballPlayerWriteAgg implements BizValidator {// 操作类型private Integer maintainType;// 维护信息private MaintainVO maintainInfo;// 运动员信息private FootballPlayerEntity playInfo;@Overridepublic void validate() {AssertUtil.notNull(maintainType, new BizError);AssertUtil.notNull(maintainInfo, new BizError);AssertUtil.notNull(playInfo, new BizError);if(maintainType = MaintainEnum.CREATE.getType()) {AssertUtil.notNull(maintainInfo.getCreator(), new BizError);AssertUtil.notNull(maintainInfo.getCreateTime(), new BizError);}if(maintainType = MaintainEnum.UPADTE.getType()) {AssertUtil.notNull(maintainInfo.getUpdator(), new BizError);AssertUtil.notNull(maintainInfo.getUpdateTime(), new BizError);}}
}

3.2.5 数据传输对象

DTO(Data Transfer Object)用于接收或传输外部数据,只应该暴露必要信息:

public class FootballPlayerCreateDTO {// 运动员姓名private String name;// 运动员身高private Integer height;// 运动员体重private Integer weight;// 跑动距离private Double runDistance;// 传球成功率private Double passSuccess;// 进球数private Integer scoreNum;// 创建人private String creator;// 创建时间private Date createTime;
}public class FootballPlayerUpdateDTO {// 运动员IDprivate Long id;// 运动员姓名private String name;// 运动员身高private Integer height;// 运动员体重private Integer weight;// 跑动距离private Double runDistance;// 传球成功率private Double passSuccess;// 进球数private Integer scoreNum;// 修改人private String updator;// 修改时间private Date updateTime;
}public class FootballPlayerQueryDTO {// 运动员IDprivate Long playerId;// 页数private Integer pageNum;// 条数private Integer size;
}public class FootballPlayerSimpleResultDTO {// 运动员IDprivate Long playerId;// 运动员姓名private String playerName;
}

4 领域事件

当某个领域发生一件事情时,如果其它领域有后续动作跟进,我们把这件事情称为领域事件,这个事件需要被感知。

球员比赛受伤,这是比赛域事件,但是医疗和训练域是需要感知的,那么比赛域发出一个事件,医疗和训练域会订阅。球员比赛取得进球,这也是比赛域事件,但是训练和合同域也会关注这个事件,所以比赛域也会发出一个比赛进球事件,训练和合同域会订阅。

通过事件交互有一个问题需要注意,通过事件订阅实现业务只能采用最终一致性,需要放弃强一致性,可能会引入新的复杂度需要权衡。

同一个进程间事件交互可以使用EventBus,跨进程事件交互可以使用RocketMQ等消息中间件。

5 代码结构

5.1 六层结构

DDD代码实现方案不尽相同,我认为不能为使用DDD而是使用DDD,而是应该根据实际情况选择当前最合适的方案。但是无论是什么方案都需要遵循一个原则:合理分层。本文将项目分为六层结构:

(1) API

接口层:提供面向外部接口声明、DTO

(2) controller

访问层:提供HTTP访问入口

(3) service

业务层:领域层和业务层都包含业务,业务层可以组合不同领域业务,并且可以实现流控、监控、日志、权限功能,相较于领域层更为丰富

(4) domain

领域层:提供Entity、VO、Agg、事件,聚合对象使用充血模型

(5) integration

整合层:访问外部限界上下文服务,解析为本限界上下文聚合对象

(6) infrastructure

基础层:提供PO、持久化服务

5.2 代码实例

如果player-core-service作为maven parent,那么其具有以下maven module和分包:

> player-core-service> player-core-api> dto> facade> constants> player-core-controller> controller> adapter1 (DTO -> Agg)> player-core-service> bizService> adapter2 (Agg -> PO)> facadeService> adapter3 (Agg -> DTO)> player-core-domain> vo> entity> agg> event> player-core-integration> proxy> adapter4 (DTO -> Agg)> player-core-infrastructure> po> mapper

5.3 如何取舍

上述项目有六层结构,那么必然带来层次间调用对象互相转换这个问题:

adapter1接收外部请求(DTO)需要转换成(Agg)
adapter2处于业务层(业务处理操作数据库)(Agg)需要转换成(PO)
adapter3处于对外业务层(暴露RPC)(Agg)需要转换成(DTO)
adapter4处于整合层(访问外部RPC)(DTO)需要转换成(Agg)

对象转换会带来两个问题:第一个是代码复杂度增加,第二个是有一定性能损耗。这也是分层结构必须要付出之代价。

因为每层对象看似相同(具有相同属性或者结构)但是语义和角色完全不同,每一层可以为对象新增这一层之特性,相较于一个对象贯穿始终,可扩展性显著提升。

6 文章总结

第一章节回顾《结合DDD讲清楚编写技术方案七大维度》这篇文章并且提出扩展两个维度:概念映射与代码结构,第二三四章节对应扩展第一个维度概念映射,第五章节对应扩展第二个维度代码结构,希望本文对大家有所帮助。

欢迎大家关注公众号「JAVA前线」查看更多精彩分享文章,主要包括源码分析、实际应用、架构思维、职场分享、产品思考等等,同时欢迎大家加我个人微信「java_front」一起交流学习

《结合DDD讲清楚编写技术方案的七大维度》再讨论相关推荐

  1. 【呕心沥血】整理全栈自动化测试技术(三):如何编写技术方案

    前面两篇笔记我介绍了自动化测试前期调研注意事项和前置准备阶段切入点,有同学在后台提问: "做完前期的调研和准备工作,领导要求写一个落地方案并评审,自动化测试的落地方案该怎么写"? ...

  2. 程序员如何讲清楚技术方案

    最近在评审技术方案,和代码review的时候,遇到刚入行的同学们,很多都讲不清楚技术方案. 具体表现是: – 上来不说需求,直接说算法实现.台下一头雾水,根本不知道设计方案是否合理. – 描述完需求后 ...

  3. 如何写一篇可实施的技术方案?

    为何要写这篇博文? 在日常开发中,老大经常要求我们给出一个完善并合理的技术方案之后才能进行开发.并且要求技术方案一定要细,要重点覆盖监控.异常处理.灰度.降级方案.同时要注重边界处理.最初,我的技术方 ...

  4. Android APK加壳技术方案----代码实现

    本文章由Jack_Jia编写,转载请注明出处. 文章链接:http://blog.csdn.net/jiazhijun/article/details/8746917 作者:Jack_Jia    邮 ...

  5. 每日百万订单,这样的技术方案更靠谱

    大家好,我是涛哥. 前段时间发了一篇原创文章"日订单量达到100万单后,我们做了订单中心重构",得到了不少读者的好评,也被不少公众号转发了30多次.根据读者反馈,我对本文做了部分优 ...

  6. 技术天地 | CSS-in-JS:一个充满争议的技术方案

    导读 为了解决传统CSS在现代前端应用开发中遇到的痛点,FreeWheel评估了大量新一代的CSS框架/工具/方案.在本文中,作者以评估过程为线索,介绍了CSS-in-JS的背景.现状.开发特点和趋势 ...

  7. vivo不小心把内部自研技术方案写进了“年终总结”,我看了直接好家伙

    杨净 萧箫 发自 凹非寺 量子位 报道 | 公众号 QbitAI 自研专业影像芯片V1,可1秒内处理30张与普通手机像素相同的照片,搭载该芯片的品牌,在第三季度成为4000+以上高端市场份额TOP 3 ...

  8. 云架构指挥调度平台技术方案建议书

    1.1项目概况 本文提出的技术方案就是利用先进的软交换技术为客户提供搭建一个完整的基于通信云计算架构的指挥调度系统平台,并为其提供及时.可靠的技术保障. 1.2业务需求 基于我们对于云指挥调度系统平台 ...

  9. 第16届全国大学生智能汽车竞赛——百度智慧交通赛项技术方案公开

    目录 一. 前言 二. 方案介绍 1. 巡线 1.1 遥控 1.2 光线 1.3 分段 2. 举旗 3. 打靶 3.1 调整车头方向 3.2 靶心定位 3.3 距离控制 4. 宿营 5. 抓物块 6. ...

最新文章

  1. c语言运动会分数统计系统_初学C语言Bug大赏
  2. Redmine使用指南
  3. 深度学习在自然语言处理研究上的进展
  4. 3.7 感知器-机器学习笔记-斯坦福吴恩达教授
  5. baidumap vue 判断范围_懂一点前端—Vue快速入门
  6. q7goodies事例_Java 8 Friday Goodies:SQL ResultSet流
  7. zuul集成cloud_如何在具有持续集成的Google Cloud Run上运行Laravel-分步指南
  8. 丁磊:阿里网易员工很多是夫妻
  9. 收拾老家发现的老版纸币,现在还能用吗?
  10. Javaweb重要知识点总结(六)常见的前端框架
  11. Windows无法访问指定设备路径或文件,您可能没有合适的权限访问这个项目
  12. office 打开wps乱_为什么word文档用wps打开,格式乱了
  13. Git 每次提交都需要输入密码
  14. 教你如何从官网下载ipp,并在vs里面跑起来
  15. 读书随想2 - 格鲁夫给经理人的第一课
  16. word中图片为嵌入式格式时显示不全_打印Word图片显示不全 Word2007图片显示不全解决方法...
  17. 全国计算机四级之网络工程师知识点(五)
  18. pcl计算点云法向量
  19. 2014 hack.lu oreo house of sprit
  20. Date,LocalDateTime类型,获取今年开始时间,获取去年的今天。

热门文章

  1. h-ui天气预报HTML页面代码,AE-UI界面动效天气预报
  2. Linux htop状态含义
  3. zookeeper原理及应用
  4. 湖南卫视节目单2013年3月17日
  5. C语言库函数中的Strcat函数
  6. 技术培训|RAC 宕机罪犯案情探析之子游标预告
  7. python中sum函数的使用方法及实例_sum函数的使用方法及实例
  8. 影子战术:将军之刃_神秘的指挥车:看战术包
  9. 龙珠激斗获取服务器信息中,龙珠激斗孙悟空碎片获取途径一览
  10. root快速关闭,如何关闭root功能