当前位置:数据分析 > 领域驱动设计 DDD 实际项目实施的最佳实践

领域驱动设计 DDD 实际项目实施的最佳实践

  • 发布:2023-10-05 13:41

领域驱动设计(DDD)设计思想和方法论早在 2005 年就被提出,但直到 2015 年才被重视和推荐,多年后,微服务流行后才受到重视并再次推荐。

介绍一下DDD的设计思想和方法论,并结合我们在实际项目中的应用总结和思考。

目录

1。为什么要使用DDD

2。 DDD架构设计

3。领域建模方法

4。代码练习

5。问题总结

1。为什么要使用DDD


面向过程
很多时候,我们使用面向对象的语言来做面向过程的活动。大多数系统都是从单一业务开始的。然而,随着支持的业务越来越多,代码中开始出现大量的if-else逻辑。这时候,代码就开始有臭味了。没闻到的同学会继续堆积,闻到的同学会重构。但由于系统没有统一的可扩展架构,重构的手法也各不相同,这种代码不一致也是一个理解上的复杂度。

分层不合理
分层太多或没有分层都会导致系统复杂度增加。

随心所欲
随心所欲,因为缺乏规范和约束。

面向对象
对面向对象的设计原理、模式、方法论有很多深入的理解,必须有非常好的业务理解和抽象能力。
SOLID 是单一职责原则(SRP)、开闭原则(OCP)、里氏替换原则(LSP)、接口隔离原则(ISP)和依赖倒置原则(DIP)的缩写。原则比模式更基本。更重要的准则,深入理解面向对象的设计原理可以极大地提高我们面向对象的设计能力和代码质量。

分层设计
分层最大的好处就是分离关注点,让每一层只解决该层关注的问题,从而将复杂问题简单化,起到分而治之的作用。

领域建模
领域模型可以准确反映业务语言,使业务语义明确,而传统的事务性编程模型如J2EE或Spring+Hibernate/Mybatis只关心数据。这些数据对象除了简单的setter之外,除了/getter方法之外没有任何业务方法,这就是所谓的贫血模式。

标准化设计归其各个职位、命名协议、通用业务状态代码协议等

DDD概念定义

领域驱动设计:一种软件开发方法,是处理软件核心复杂性的一种方式。它可以帮助我们设计高质量的软件模型。

DDD 目的:

  1. DDD就是解决复杂的业务逻辑问题
  2. DDD的核心问题不是技术问题,而是讨论、倾听、理解、发现商业价值
  3. DDD是技术人员产品思维的体现
  4. DDD的学习曲线比较陡,主要是因为它是一种方法论,每个人对它的理解不一样

领域模型与交易脚本对比

0 {IMG_0: Ahr0Chm6ly9pbwcymdizlmnuymxvz3Muy2JSB2CVMTYWMDG4LZIWMWMY8XNJAWODGTMJAYMJAYMTE1MDQ2MDKTMTE2NZC1NI5WBBBBBBBBBBBBBA mc =/}

丰富的模型:即属性和方法都封装在Domain Object中,所有业务直接操作Domain Object。服务层只是完成一些简单的事务等,甚至不需要使用服务层。即直接从action->entity。

贫血模型:表示对象中只有属性。业务的实现,必须依赖服务层来实现相关的方法。服务层的实现是面向流程的,大量传统分层应用的action->service->dao->entity方法就是这种贫血模型的实现。

例如银行转账实现类

亲爱的读者们,看到上面的代码有没有一种似曾相识的感觉呢?乍一看,并没有什么问题。能够正常运行并快速交付业务。如果只是为了满足需求和任务交换,当然没有问题。

从设计角度来说确实存在一些问题。代码混乱,没有分层设计,伪面向对象的方式。

我们程序员一定要有自己的追求,为伟大的编码事业创造一些艺术贡献!

  • 业务需求变化很快,需求越来越复杂怎么办?
  • 如果团队中有多人协作,代码需要多人反复修改、传递,你能保证后来接手的人不会骂你吗?
  • 设计上还有改进的空间吗?

答案是肯定的!

客观来说,上面的代码并不复杂,也算写得不错了。现在让我发布一段令人惊叹的代码,该代码已经在我们的实际生产系统中运行了近一年。只有这样你才会知道什么是复杂和神奇! 16层if..else+for循环!就问你害怕吗?

》》请点击下方观看神奇代码片段👇👇

请看神典——十八层地狱!
 if (contentOld != null && contentNew != null) {
map.put("ModelId", contentNew.getModelId());//ModelId
map.put("name", contentNew.getName());//名称
map.put("描述", contentNew.getDescription());//描述
map.put("fujianGroup",attachments);//附件组信息
for (int i = 0; i < contentOld.getGroups().size(); i++) {//数据库保存组MscsApiShowerRoomModelGroups groupsOld = contentOld.getGroups().get(i);
if ("Nano Easy Knot".equals(groupsOld.getGroupName())) {//新增群组信息
map.put("nanometerGroup", groupsOld);//纳米易结
} else if ("世纪第一".equals(groupsOld.getGroupName())) {
map.put("stoneGroupFirst", groupsOld);//石基启动
} else if ("客户其他内容".equals(groupsOld.getGroupName())) {
map.put("otherGroup", groupsOld);//其他客户内容
} else {//原组由组ID确定
if ("1".equals(groupsOld.getGroupId())) {//产品规格组
for (MscsApiShowerRoomModelGroups groups : contentNew.getGroups()) {
if (groupsOld.getGroupId().equals(groups.getGroupId())) {
groups.setOtherValue(groupsOld.getOtherValue());//设置单选按钮
if (groupsOld.getGategories() != null && groups.getGategories() != null) {
if (groupsOld.getGategories() != null && groups.getGategories() != null) {
for (MscsApiShowerRoomModelCategories 类别: groupsOld.getGategories()) {
for (MscsApiShowerRoomModelCategoriescategories2 : groups.getGategories()) {if (categories.getOptions() != null &&categories2.getOptions() != null) {
for (MscsApiShowerRoomModelOptions 选项:categories.getOptions()) {
for (MscsApiShowerRoomModelOptions options2 :categories2.getOptions()) {
if (options.getOptionId() != null && options.getOptionId().equals(options2.getOptionId())) {
options2.setIsDefault(true);//设置为默认选项
for (MscsApiShowerRoomModelProperties 属性:options.getProperties()) {
对于(MscsApiShowerRoomModelProperties属性2:选项2.getProperties()){
if (properties.getPropertyId() != null &&properties.getPropertyId().equals(properties2.getPropertyId())) {
property2.setDefaults(properties.getDefaults());
}
}
}
} 别的 {
options2.setIsDefault(false);
}
}
}
}
}
}
}
}
map.put("productGroup", groups);//产品规格组
}
}
// map.put("productGroup",null);//产品规格组
} else if ("2".equals(groupsOld.getGroupId())) {//开门方向与产品方向
for (MscsApiShowerRoomModelGroups groups : contentNew.getGroups()) {if (groupsOld.getGroupId().equals(groups.getGroupId())) {
if (groupsOld.getGategories() != null && groups.getGategories() != null) {
if (groupsOld.getGategories() != null && groups.getGategories() != null) {
for (MscsApiShowerRoomModelCategories 类别: groupsOld.getGategories()) {
for (MscsApiShowerRoomModelCategoriescategories2 : groups.getGategories()) {
if (categories.getOptions() != null &&categories2.getOptions() != null) {
for (MscsApiShowerRoomModelOptions 选项:categories.getOptions()) {
for (MscsApiShowerRoomModelOptions options2 :categories2.getOptions()) {
if (options.getOptionId() != null && options.getOptionId().equals(options2.getOptionId())) {
options2.setIsDefault(true);//设置为默认选项
} 别的 {
options2.setIsDefault(false);
}
}
}
}
}
}
}
}
map.put("doorGroup", groups);//开门方向与产品方向
}
}
} else if ("3".equals(groupsOld.getGroupId())) {//产品颜色
for (MscsApiShowerRoomModelGroups groups : contentNew.getGroups()) {if (groupsOld.getGroupId().equals(groups.getGroupId())) {
if (groupsOld.getGategories() != null && groups.getGategories() != null) {
if (groupsOld.getGategories() != null && groups.getGategories() != null) {
for (MscsApiShowerRoomModelCategories 类别: groupsOld.getGategories()) {
for (MscsApiShowerRoomModelCategoriescategories2 : groups.getGategories()) {
if (categories.getOptions() != null &&categories2.getOptions() != null) {
for (MscsApiShowerRoomModelOptions 选项:categories.getOptions()) {
for (MscsApiShowerRoomModelOptions options2 :categories2.getOptions()) {
if (options.getOptionId() != null && options.getOptionId().equals(options2.getOptionId())) {
options2.setIsDefault(true);//设置为默认选项
} 别的 {
options2.setIsDefault(false);
}
}
}
}
}
}
}
}
map.put("productColorGroup", groups);//产品颜色
}
}
} else if ("14".equals(groupsOld.getGroupId())) {//玻璃工艺
字符串名称=“”;for (MscsApiShowerRoomModelGroups groups : contentNew.getGroups()) {
if (groupsOld.getGroupId().equals(groups.getGroupId())) {
groups.setOtherValue(groupsOld.getOtherValue());
if (groupsOld.getGategories() != null && groups.getGategories() != null) {
for (MscsApiShowerRoomModelCategories 类别: groupsOld.getGategories()) {
for (MscsApiShowerRoomModelCategoriescategories2 : groups.getGategories()) {
if (categories.getName() != null &&categories.getName().equals(categories2.getName())) {
名称 = 类别.getName();
if (categories.getOptions() != null &&categories2.getOptions() != null) {
for (MscsApiShowerRoomModelOptions 选项:categories.getOptions()) {
for (MscsApiShowerRoomModelOptions options2 :categories2.getOptions()) {
if (options.getOptionId() != null && options.getOptionId().equals(options2.getOptionId())) {
options2.setIsDefault(true);//设置为默认选项
} 别的 {
options2.setIsDefault(false);
}
}
}
}
}
}
}
}//设置默认数据的位置。所选阵列是第一个阵列,默认显示。
if (groups.getGategories() != null && !"".equals(name)) {
List arry = new ArrayList();
List arry1 = new ArrayList();
List arry2 = new ArrayList();
for (MscsApiShowerRoomModelCategories 类别: groups.getGategories()) {
if (name.equals(categories.getName())) {
arry1.add(类别);
} 别的 {
arry2.add(类别);
}
}
for (MscsApiShowerRoomModelCategories a : arry1) {
arry.add(a);
}
for (MscsApiShowerRoomModelCategories a : arry2) {
arry.add(a);
}
groups.setGategories(arry);
}
map.put("glassGroup", groups);//玻璃工艺
}
}
// map.put("glassGroup", null);//玻璃工艺
} else if ("4".equals(groupsOld.getGroupId())) {//玻璃膜
for (MscsApiShowerRoomModelGroups groups : contentNew.getGroups()) {
if (groupsOld.getGroupId().equals(groups.getGroupId())) {
groups.setOtherValue(groupsOld.getOtherValue());
if (groupsOld.getGategories() != null && groups.getGategories() != null) {for (MscsApiShowerRoomModelCategories 类别: groupsOld.getGategories()) {
for (MscsApiShowerRoomModelCategoriescategories2 : groups.getGategories()) {
if (categories.getName() != null &&categories.getName().equals(categories2.getName())) {
if (categories.getOptions() != null &&categories2.getOptions() != null) {
for (MscsApiShowerRoomModelOptions 选项:categories.getOptions()) {
for (MscsApiShowerRoomModelOptions options2 :categories2.getOptions()) {
if (options.getOptionId() != null && options.getOptionId().equals(options2.getOptionId())) {
options2.setIsDefault(true);//设置为默认选项
} 别的 {
options2.setIsDefault(false);
}
}
}
}
}
}
}
}
map.put("filmGroup", groups);//玻璃贴膜
//								休息 ;
}
}
} else if ("5".equals(groupsOld.getGroupId())) {//玻璃厚度
for (MscsApiShowerRoomModelGroups groups : contentNew.getGroups()) {
if (groupsOld.getGroupId().equals(groups.getGroupId())) {if (groupsOld.getGategories() != null && groups.getGategories() != null) {
for (MscsApiShowerRoomModelCategories 类别: groupsOld.getGategories()) {
for (MscsApiShowerRoomModelCategoriescategories2 : groups.getGategories()) {
if (categories.getName() != null &&categories.getName().equals(categories2.getName())) {
if (categories.getOptions() != null &&categories2.getOptions() != null) {
for (MscsApiShowerRoomModelOptions 选项:categories.getOptions()) {
for (MscsApiShowerRoomModelOptions options2 :categories2.getOptions()) {
if (options.getOptionId() != null && options.getOptionId().equals(options2.getOptionId())) {
options2.setIsDefault(true);//设置为默认选项
} 别的 {
options2.setIsDefault(false);
}
}
}
}
}
}
}
}
map.put("thicknessGroup", groups);//玻璃厚度
}
}
} else if ("6".equals(groupsOld.getGroupId())) {//拉手款式
字符串名称=“”;
for (MscsApiShowerRoomModelGroups groups : contentNew.getGroups()) {if (groupsOld.getGroupId().equals(groups.getGroupId())) {
if (groupsOld.getGategories() != null && groups.getGategories() != null) {
for (MscsApiShowerRoomModelCategories 类别: groupsOld.getGategories()) {
for (MscsApiShowerRoomModelCategoriescategories2 : groups.getGategories()) {
if (categories.getName() != null &&categories.getName().equals(categories2.getName())) {
名称 = 类别.getName();
if (categories.getOptions() != null &&categories2.getOptions() != null) {
for (MscsApiShowerRoomModelOptions 选项:categories.getOptions()) {
for (MscsApiShowerRoomModelOptions options2 :categories2.getOptions()) {
if (options.getOptionId() != null && options.getOptionId().equals(options2.getOptionId())) {
options2.setIsDefault(true);//设置为默认选项
} 别的 {
options2.setIsDefault(false);
}
}
}
}
}
}
}
}
//设置默认数据的位置选中的库存为第一个默认恢复
if (groups.getGategories() != null && !"".equals(name)) {
List arry = new ArrayList();List arry1 = new ArrayList();
List arry2 = new ArrayList();
for (MscsApiShowerRoomModelCategories 类别: groups.getGategories()) {
if (name.equals(categories.getName())) {
arry1.add(类别);
} 别的 {
arry2.add(类别);
}
}
for (MscsApiShowerRoomModelCategories a : arry1) {
arry.add(a);
}
for (MscsApiShowerRoomModelCategories a : arry2) {
arry.add(a);
}
groups.setGategories(arry);
}
map.put("handleGroup", groups);//拉手款式
}
}
// map.put("handleGroup", null);//拉手款式
} else if ("7".equals(groupsOld.getGroupId())) {//石基
字符串名称=“”;
for (MscsApiShowerRoomModelGroups groups : contentNew.getGroups()) {
if (groupsOld.getGroupId().equals(groups.getGroupId())) {
if (groupsOld.getGategories() != null && groups.getGategories() != null) {
for (MscsApiShowerRoomModelCategories 类别: groupsOld.getGategories()) {
for (MscsApiShowerRoomModelCategoriescategories2 : groups.getGategories()) {if (categories.getName() != null &&categories.getName().equals(categories2.getName())) {
名称 = 类别.getName();
if (categories.getOptions() != null &&categories2.getOptions() != null) {
for (MscsApiShowerRoomModelOptions 选项:categories.getOptions()) {
for (MscsApiShowerRoomModelOptions options2 :categories2.getOptions()) {
if (options.getOptionId() != null && options.getOptionId().equals(options2.getOptionId())) {
options2.setIsDefault(true);//设置为默认选项
} 别的 {
options2.setIsDefault(false);
}
}
}
}
}
}
}
}
//设置默认数据的位置。所选阵列是第一个阵列,默认显示。
if (groups.getGategories() != null && !"".equals(name)) {
List arry = new ArrayList();
List arry1 = new ArrayList();
List arry2 = new ArrayList();
for (MscsApiShowerRoomModelCategories categories : groups.getGategories()) {
if (name.equals(categories.getName())) {
arry1.add(categories);
} else {
arry2.add(categories);
}
}
for (MscsApiShowerRoomModelCategories a : arry1) {
arry.add(a);
}
for (MscsApiShowerRoomModelCategories a : arry2) {
arry.add(a);
}
groups.setGategories(arry);
}
map.put("stoneGroup", groups);//拉手款式
}
}
} else if ("8".equals(groupsOld.getGroupId())) {//层架
for (MscsApiShowerRoomModelGroups groups : contentNew.getGroups()) {
if (groupsOld.getGroupId().equals(groups.getGroupId())) {
if (groupsOld.getGategories() != null && groups.getGategories() != null) {
if (groupsOld.getGategories() != null && groups.getGategories() != null) {
for (MscsApiShowerRoomModelCategories categories : groupsOld.getGategories()) {
for (MscsApiShowerRoomModelCategories categories2 : groups.getGategories()) {
if (categories.getOptions() != null && categories2.getOptions() != null) {
for (MscsApiShowerRoomModelOptions options : categories.getOptions()) {
for (MscsApiShowerRoomModelOptions options2 : categories2.getOptions()) {
if (options.getOptionId() != null && options.getOptionId().equals(options2.getOptionId())) {
options2.setIsDefault(true);//设置为默认选项
for (MscsApiShowerRoomModelProperties properties : options.getProperties()) {
for (MscsApiShowerRoomModelProperties properties2 : options2.getProperties()) {
if (properties.getPropertyId() != null && properties.getPropertyId().equals(properties2.getPropertyId())) {
properties2.setDefaults(properties.getDefaults());
}
}
}
} else {
options2.setIsDefault(false);
}
}
}
}
}
}
}
}
map.put("shelfGroup", groups);//层架
}
}
//						map.put("shelfGroup", null);//层架
} else if ("9".equals(groupsOld.getGroupId())) {//木架包装
for (MscsApiShowerRoomModelGroups groups : contentNew.getGroups()) {
if (groupsOld.getGroupId().equals(groups.getGroupId())) {
if (groupsOld.getGategories() != null && groups.getGategories() != null) {
if (groupsOld.getGategories() != null && groups.getGategories() != null) {
for (MscsApiShowerRoomModelCategories categories : groupsOld.getGategories()) {
for (MscsApiShowerRoomModelCategories categories2 : groups.getGategories()) {
if (categories.getOptions() != null && categories2.getOptions() != null) {
for (MscsApiShowerRoomModelOptions options : categories.getOptions()) {
for (MscsApiShowerRoomModelOptions options2 : categories2.getOptions()) {
if (options.getOptionId() != null && options.getOptionId().equals(options2.getOptionId())) {
options2.setIsDefault(true);//设置为默认选项
} else {
options2.setIsDefault(false);
}
}
}
}
}
}
}
}
map.put("mujiaGroup", groups);//木架包装
}
}
} else if ("32".equals(groupsOld.getGroupId())) {//玻璃宽度
for (MscsApiShowerRoomModelGroups groups : contentNew.getGroups()) {
if (groupsOld.getGroupId().equals(groups.getGroupId())) {
if (groupsOld.getGroupId().equals(groups.getGroupId())) {
if (groupsOld.getGategories() != null && groups.getGategories() != null) {
if (groupsOld.getGategories() != null && groups.getGategories() != null) {
for (MscsApiShowerRoomModelCategories categories : groupsOld.getGategories()) {
for (MscsApiShowerRoomModelCategories categories2 : groups.getGategories()) {
if (categories.getOptions() != null && categories2.getOptions() != null) {
for (MscsApiShowerRoomModelOptions options : categories.getOptions()) {
for (MscsApiShowerRoomModelOptions options2 : categories2.getOptions()) {
if (options.getOptionId() != null && options.getOptionId().equals(options2.getOptionId())) {
options2.setIsDefault(true);//设置为默认选项
if (options.getProperties() != null) {
for (MscsApiShowerRoomModelProperties properties : options.getProperties()) {
for (MscsApiShowerRoomModelProperties properties2 : options2.getProperties()) {
if (StringUtils.isNotEmpty(properties.getCode()) && properties.getCode().equals(properties2.getCode())) {
properties2.setDefaults(properties.getDefaults());
}
}
}
}
} else {
options2.setIsDefault(false);
}
}
}
}
}
}
}
}
map.put("glassWidthGroup", groups);// 玻璃宽度
}
}
}
}
}
}
}

 

各位看官你们看,看完是不是很想吐血!像上面这种代码请问谁敢去维护谁看谁骂,谁改谁死!这种神代码绝对是可以拿来当教材用的~(在这里展示这段代码主要为了说明如果系统设计和分层不合理,将会带来严重的后果,可以说代码就像狗屎一样)

PS:说明一下当时写这些代码的作者因为一些原因离职了,我们当年系统上线后将近一年多的时间里不敢去修改这神代码,也没有人敢改。

如果业务需求一直稳定不改,那可以还勉强维持着,但是那有正常业务不改需求的?天底下应该没有!所以它始终像一颗大雷在我们的头顶上滚动着!

后来经过两次重大重构设计之后,把它消灭了!篇幅原因这里的故事就不展开讲了,有兴趣的请看另一篇文章:《那些年那些神码》。

回到主题上,银行转账那一段代码对比之下是不是小屋见大屋?下面我们基于银行转账那一段代码,展示一下怎么拆解和设计,变成领域设计模型。

转变成领域设计如下:

  • 抽出Account Domain类;
  • 抽出转账类即领域服务;
  • 抽出透支策略接口与实现类

重新拆解转换之后,设计领域对象,代码分层结构清晰了很多,后续业务折腾变化容易维护和扩展,从此世界也变得清新了,你说香不香吗?

 

2、领域驱动设计架构设计

2.1、分层结构转变

先看分层结构思路转变,领域驱动设计分层与传统三层结构有比较直观的区别。

User Interface为用户接口层,也是经常我们看到Controller层类似,主要负责系统入口协议等,该层不处理任何业务逻辑,只负责调用接入和应用转发。

Application是应用层,和以往事务脚本的Service是截然不同的,该层中并不做详细的业务逻辑的封装,而是创建Domian并调用Domain中的相应方法完成业务逻辑处理,并调用Infrastructure完成Domain的持久化操作,该层需要负责事务的控制,保证数据的一致性。

Domain层是核心领域层,核心的业务逻辑应该以Domain为对象进行分类封装,Domain的划分以业务为基准,Domain层在技术层面的建模通用技巧在下面会做详细介绍,该层只能向下调用Infrastructure完成自身模型的初始化和持久化。

Infrastructure层类似于以往事务脚本的Dao层,以往的面向事务脚本的编程中,以数据表为主,所有的事务脚本直指目的就是完成表的CURD,而DDD中以模型为核心,Infrastructure层是为了重建已有的Domain,并在退出时持久化内存中的Domain对象。Infrastructure层不仅包含对关系数据库的处理,还包括对分布式缓存处理、对外系统的接入(integration)以及分布式消息队列的push操作。

一些常用的关键术语:

  • 实体 - Entity
  • 值对象 - Value Objects
  • 领域服务 - Domain Services
  • 领域事件 - Domain Events
  • 模块 - Modules
  • 聚合 - Aggregate
  • 资源库 - Repository

实体和值对象在代码中皆表示为一个类(对象),从业务上来讲也分别表示一个业务实体。但是两者是有区别,实体是一个完整的具有生命周期的可以通过唯一标识来识别东西的类(对象),而值对象则为了度量和描述领域中的一个属性,将不同的相关属性组合成一个概念整体的类(对象) 。 

例如,客户可以认为是一个实体,一个客户就是具有生命周期的东西,具有唯一的标识可以将A客户和B客户分开(唯一标识:身份证号码),而这个客户的地址(例如:广州市/白云区/欧派软件园)就应该定义为一个值对象,当我们定义好一个地址,它既可以是A客户的地址也可以是B客户的地址,当我需要更新A客户地址时,可以直接销毁A客户关联的地址对象然后重新创建一个地址对象关联到A客户上。

领域事件是由一个特定领域因为一个用户Command触发的发生在过去的行为产生的事件,而这个事件是系统中其它部分感兴趣的。

在现在的分布式环境下,业务系统与业务系统之间并不是割裂的,而消息绝对是系统之间耦合度最低,最健壮,最容易扩展的一种通信机制。

领域服务和以往事务脚本的Service只能说非常像,唯一不同的是,以往事务脚本的Service是自己全权负责业务逻辑,而领域服务不仅编写业务逻辑,还会调用实体和值对象的方法来协调实体和值对象,从而避免实体和值对象的耦合。当我们建模之后发现有些业务既不单单属于A领域对象,又不单单属于B领域对象,我们可以那么我们可以抽象出一个Service来完成此项业务,那么这个类就是领域服务。

资源库也叫数据仓库,主要是完成领域对象的重建以及对象的持久化操作。资源库的设计主要是为了调用基础设施层来完成表的CURD、缓存的操作以及外系统接口的调用。

 

2.2、领域驱动设计

这里引用阿里团队开源的可乐(Cola)领域设计架构示图,如下图所示:

DDD最核心思想是设计内部六边形结构。

从内往外看,最内层也是最核心就是Domain层(包括:Domain model和Domain Service);

第二层是Application层(包括:Application Service);

第三层业务逻辑层(Business Logic也可以叫基础设施层)主对外提供服务接口,对内解决基础服务包装构建。

 

2.3、分层调用示图

DDD分层架构设计之调用示图

 

 

上图已经非常清楚展示了分层设计以及各层调用关系,一图胜千言,大家认真详细看图就可以理解了。

2.4、命令式读写分离

操作命令和对象抽象,通过命令与查询分离设计方式实现服务接口调用。

CQRS(Command Query Separation,命令查询分离) 基本思想在于,任何一个对象的方法可以分为两大类:

  • 命令(Command):不返回任何结果(void),但会改变对象的状态
  • 查询(Query):返回结果,但是不会改变对象的状态,对系统没有副作用

 

2.5、扩展点

领域驱动设计另外一个重要的地方是扩展点。

可乐包在扩展点功能设计中引入的概念:业务、用例、场景。

  • 业务(Business):就是一个自负盈亏的财务主体,比如tmall、淘宝和零售通就是三个不同的业务。
  • 用例(Use Case):描述了用户和系统之间的互动,每个用例提供了一个或多个场景。比如,支付订单就是一个典型的用例。
  • 场景(Scenario):场景也被称为用例的实例,包括用例所有的可能情况(正常的和异常的)。比如对于“订单支付”这个用例,就有“可以使用花呗”,“支付宝余额不足”,“银行账户余额不足”等多个场景。

 

 

例如我们要实现右图中所展示的扩展:在tmall这个业务下——下单用例——88VIP场景——用户身份校验进行扩展,我们只需要声明一个如下的扩展实现(Extension)就可以了。

 

 

3、领域建模

领域架构设计并不复杂,复杂的业务需求怎么转化为领域模型,这也是最难的地方,需要业务梳理足够清晰,同时需要有足够抽象能力和领域划分。

领域建模基于业务的建模的基础上,需要花较重的时间比例在梳理业务和模型设计上面;而同时领域建没有通用的统一结构设计,得看具体业务具体分析。下面举个例子说明一下领域建模方法。希望能够各位得到一点启发。

领域模型不是简单POJO或数据实体对象,他还有行为和状态,主要体现在事件机制、值对象上面。

这里先不深入讨论,先抛个影子,后面抽空补上更详细的说明。

 

 

4、领域驱动设计实现

 

4.1、分层设计

各层分工:

  • Application层主要负责获取输入,组装context,做输入校验,发送消息给领域层做业务处理,监听确认消息;
  • Domain层主要是通过领域服务(Domain Service),领域对象(Domain Object)的交互,对上层提供业务逻辑的处理,然后调用下层Repository做持久化处理;
  • Infrastructure层主要包含Repository,Config,Common和message,Repository负责数据的CRUD操作,数据来源可以是MySQL,NoSql,Search,甚至是HSF等;
  • Config负责应用的配置;Common是一写工具类;负责message通信的也应该放在这一层。

4.2、建立二方库组件

二方库组件:
api:存放的是应用对外的接口。
dto.domainmodel:用来做数据传输的轻量级领域对象。
dto.domainevent: 用来做数据传输的领域事件。
Application里的组件:
service:接口实现的facade,没有业务逻辑,可以包含对不同终端的adapter。
eventhandler:处理领域事件,包括本域的和外域的。
executor:用来处理(Command)和查询(Query)。
interceptor:COLA提供的对所有请求的AOP处理机制。
Domain里的组件:
domain:领域实体,允许继承domainmodel。
domainservice: 领域服务,用来提供更粗粒度的领域能力。
gateway:对外依赖的网关接口,包括存储、RPC、Search等。

 

大部分情况下,二方库的确是用来定义服务接口和数据协议的。但是二方库区别于JSON的地方是它不仅仅是协议,它还是一个Java对象,一个Jar包。既然是Java对象,就意味着我们就有可能让DTO升级为一个Domain Model(有数据,有行为,有继承) 。
Domain Model用到的所有数据如果都是自恰的,那么这些计算是不需要借助外面的辅助,自己就能完成。比如CustomerCO拥有身份证号码,那么判断当前这个CustomerCO的性别、年龄等信息时是依靠自己(身份证号码)就能完成的。是一种共享内核, CustomerCO把自己领域的知识(语言、数据和行为)通过二方库暴露出去了,假如有100个应用需要获取性别或年龄时做判断,客户端就不需要自己实现了。

4.3、上下文

应用不同Bounded Context之间的协作,要充分利用好二方库的桥梁作用。其协作方式如下图所示:

 

4.4、主要组件依赖关系

依赖关系示例,如以Customer会员业务对象举例如下图所示

 

4.5、代码实现

下面以我们系统中客户中心会员体系设计为示例介绍一下怎么使用DDD方法实现代码。

对外接口代码示例

参数校验

 

API接口服务层示例

命令总线示例

 

全局异常捕获示例代码

 

4.6、旧项目神码改造

对于旧项目代码,大家都非常头痛,旧系统经常出现一些奇怪的问题,其实就是不稳定引起的。旧系统代码都是神码具多,极难维护,谁都不愿意接手,后来经过我们重大讨论决策后决定对旧项目进行重构。

重构说起来简单,实施起来却是非常的头大,毕竟不是简单的系统,同时也是公司里面业务最重要的业务系统(订单+CRM集团业绩的入口保障),不容许出错;而旧代码库非常庞大,规模接近百万行。代码质量不堪回首,都是地狱级别。

我们痛定思痛,决定对它动用外科手术,当时顶着巨大压力说服大boss同意,游说业务、产品、测试、开发各方一起协作。在保证业务规则和逻辑前提,进行重大的重构设计,主要也是采用DDD领域驱动设计。

这次重构经历了近6个月,顶着各方巨大的压力。但经过几次重大升级发布,终于彻底改头换面,神码级旧系统完成改造。

1个java类近万行神码经过重构改造,确切的说应该重设计重新写代码结构,总结一下有几条宝贵经验:

  • 根据DDD读写分离设计,写入通过实现不同的Command执行器,查询实现不同的查询执行器。
  • 根据不同业务场景增加多个不同的扩展点,有效地解耦业务。
  • 复杂的业务规则引入规则引擎,把业务规则抽象成一条条可动态编辑和可维护规则,并实现动态加载和配置,而不是硬代码。

经过一顿猛烈改造设计,新版本的代码清爽多了!

把一万行的代码直接搞成了360行左右!

旧代码:

消灭了18层地狱式代码

改造后新代码结构:

查询统一执行器如下图所示:

扩展点抽象查询器

扩展点执行器抽象类

经过一番猛操作改造之后,代码简洁很多,变得可读可维护,从此世界清新很多,维护代价极大的变小!

 

5、问题与总结

软件的世界里没有银弹!因此领域模型还是事务脚本没有对错之分, 关键看是否合适:
  • 对于简单的业务场景,使用事务脚本,其优点是简单、直观、易上手
  • 对于复杂的业务场景,比较有效的治理办法就是领域建模,在封装业务逻辑的同时,提升了对象的内聚性和重用性,因为使用了通用语言,使得隐藏的业务逻辑得到显性化表达,使得复杂性治理成为可能。

领域驱动设计结构非常清晰,适合复杂业务场景,代码结构清晰,代码维护代会低很多。当然也需要业务建模与抽象能力,与传统面象数据开发方法有本质的不同,需要转变开发者的思维方法,对团队有一定学习成本。

要求开发者:
  • 有开发卓越软件的激情和毅力
  • 渴望学习和进步
  • 有能力理解软件模式,并懂得如何应用这些模式 
  • 有发掘不同的设计方法能力和耐性
  • 勇于改变现状
  • 希望编写出更好的代码

 

相关文章

最新资讯

热门推荐