侧边栏壁纸
博主头像
码森林博主等级

一起走进码森林,享受编程的乐趣,发现科技的魅力,创造智能的未来!

  • 累计撰写 146 篇文章
  • 累计创建 74 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录
DDD

DDD 中的一些方法和思想

码森林
2024-03-18 / 0 评论 / 0 点赞 / 89 阅读 / 4,655 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2024-04-08,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

产研现状

现状一

技术往往以技术的角度去理解业务,更多关注构建哪些表、包含哪些字段、表与表的关系、使用什么技术实现等技术细节,通过这些技术细节来判断需求能否实现、是否合理。(深入理解业务)

现状二

新需求(复杂的业务)不断加入后,代码变的越来越冗杂,软件质量退化,维护困难,越来越难以支撑业务。(解耦)

现状三

团队内缺少统一语言,产品、技术使用不同的术语描述,产生业务理解上的分歧。(通用业务语言)

现状四

产品设计阶段研发人员参与度很低,需求从客户到产品设计,再到研发实现,最终不是客户想要的。

基于以上现状,DDD 的一些设计思想和方法可以给我们一些参考建议和价值。

👇🏻我们来学习一下领域驱动中用于分析复杂业务的方法。

事件风暴与统一语言

事件风暴

事件风暴(Event Storming):一种以工作坊的方式对复杂业务领域进行探索的高效协作方法,事件风暴强调以事件驱动团队探索分析业务领域,拉通业务方、产品、研发、测试对业务知识的统一理解,避免各方理解误差。

作用:

  • 明确业务需求和流程:DDD事件风暴帮助团队了解业务的细节,理解业务过程和业务需求,从而让团队更好地理解客户的要求,并专注于创造高价值的功能。
  • 加速迭代:DDD事件风暴鼓励小型、快速的开发循环,以进行快速的原型开发和基于反馈的迭代,该方法的灵活性比传统开发更高。
  • 小组协作:DDD事件风暴是一种团队合作的方法,意味着不同职能之间的沟通和协作,使得开发和业务分析师可以更好地配合,减少方面之间的挑战和麻烦。
  • 改进团队效率:DDD事件风暴减少了开发人员的错误,帮助创造更好的代码,也提高了团队之间的效率,从而减少了开发时间和资源的浪费。
  • 更好的可维护性:DDD事件风暴创建了一种更明显的领域模型,可以更好地理解系统的各个部分,从而更容易维护和扩展代码。

主要步骤:

  • 产品愿景分析
  • 业务场景分析
  • 构建领域模型

通用语言

通用语言(Ubiquitous Language):在事件风暴过程中,通过团队交流达成共识的,能够简单、清晰、准确地描述业务含义和规则的语言就是通用语言。

图片

作用:

  • 在团队协作时能够共同的语言进行交流和协作,确保业务需求的正确表达和系统的正确实现。
  • 使开发人员更加全面地理解业务领域,能够更好地反映业务需求和实现业务功能。
  • 帮助团队共同建立起业务模型,以提高业务透明度,减少理解上的歧义。
  • 为软件系统的设计提供更加规范和一致的表述方式,使设计更加精准和稳定。
  • 通过DDD通用语言,可以更好地维护软件系统的代码和文档,避免系统设计和实现中的混乱和不一致性。

通过事件风暴进行业务分析与领域建模

第一阶段:产品愿景分析

产品愿景分析的主要目标是完成产品顶层价值设计和分析,项目团队在目标用户、核心价值、产品需要具备的核心 竞争力等方面达成一致,避免在建设过程中偏离方向。

img

第二阶段:场景分析

场景分析是从用户操作视角出发,根据业务流程或用户流程,采用用例和场景分析方法,探索领域中的典型场景,找出领域事件、实体和命令等领域对象,支撑领域建模的过程。

关键:细、共识、全员

以下以用户下单场景为例进行分析,首先我们需要先确定几个关键元素,帮助我们发散思维、捕获需求、识别联系。

  • 事件(黄色):对已发生事实的陈述(比如:订单已提交、申请单已通过等)。
    • 对内:产生某种数据,状态发生变化,触发某种流程。
    • 对外:发送某些消息。
  • 命令(蓝色):产生事件触发的动作。
  • 角色/执行者:触发命令的主体,如买家(红色)、卖家(绿色)、系统(黑色)。
  • 读模型(灰色):执行者决策执行命令时参考的视图元素。
  • 写模型(紫色):状态发生变化的对象,真实因为对象发生了变化才导致事件的发生。
  • 规则(粉色):用于描述成功触发事件的规则,是对分支条件或复杂业务规则的抽象,目的是通过降低分支复杂度聚焦主要业务流程,未来在技术实 现时可能是一些分支条件,也可能应用适合的设计模式。

事件风暴的关键元素

1、根据业务流程,识别重要的事件

通过用户旅程分析,可以识别以下购物车及订单等相关核心事件。

根据业务流程,识别重要的事件

2、识别触发事件的命令

通过事件我们可以识别其对应的命令。

识别触发事件的命令

3、识别命令的执行者和读模型

通过业务流程我们可以识别出命令的执行者及用户用于决策是否执行命令的读模型。

在分析过程中,可以根据业务流程不断挖掘关联的事件及命令。如在卖家发货时,会创建物流单,在成功创建完成物流单后,订单内明细才能变更为已发货;系统会定时更新物流单信息,触发物流已更新事件,可以通知买家等后续逻辑。

识别命令执行者和所需的读视图信息

通过团队发散还可以找到商品、库存、供应商、用户、用户地址。

可以描述领域事件触发的规则,如提交订单需要校验商品可售性和库存、收货地址,状态校验等

第三阶段:领域建模

1、提取领域对象

从命令和领域事件中提取产生这些业务行为的业务对象,即实体。通过场景分析产生的命令和事件数据,分析并提取出产生这些行为的实体,如购物车、订单、订单日志、商品、商品库存、物流单、用户信息、用户地址信息等。

提取领域对象

2、构建聚合

根据领域对象业务关系,定义聚合,定义聚合前需要先找出聚合根,如购物车、订单、商品、用户、用户地址、物流单等。

聚合:聚合内部业务逻辑高内聚,聚合之间满足低耦合的特点。聚合是领域模型中最小的业务逻辑边界,也是可拆分微服务的最小边界。

聚合有以下一些特点:

  1. 每个聚合有一个根和一个边界,边界定义了一个聚合内部有哪些实体或值对象,根是聚合内的某个实体;
  2. 聚合内部的对象之间可以相互引用,但是聚合外部如果要访问聚合内部的对象时,必须通过聚合根开始导航,绝对不能绕过聚合根直接访问聚合内的对象,也就是说聚合根是外部可以保持 对它的引用的唯一元素;
  3. 聚合内除根以外的其他实体的唯一标识都是本地标识,也就是只要在聚合内部保持唯一即可,因为它们总是从属于这个聚合的;
  4. 聚合根负责与外部其他对象打交道并维护自己内部的业务规则;
  5. 基于聚合的以上概念,我们可以推论出从数据库查询时的单元也是以聚合为一个单元,也就是说我们不能直接查询聚合内部的某个非根的对象;
  6. 聚合内部的对象可以保持对其他聚合根的引用;
  7. 删除一个聚合根时必须同时删除该聚合内的所有相关对象,因为他们都同属于一个聚合,是一个完整的概念。

3、划定限界上下文

类似如下:

img

4、领域模型的领域对象清单

领域建模过程会产生大量领域对象,领域对象在微服务设计的时候会映射到微服务代码,非常重要。为了方便管理,可以采用表格进行记录。类似如图(途中是部分领域对象,领域对象还包括值对象、领域服务、应用服务等等):

image-20230303140005249

基于 DDD 的软件设计及变更的过程

以下为电商场景,用户下单付款需求为例,了解在需求迭代的过程中如何去分析和设计软件。

初版需求

开发人员在最开始收到的关于用户付款功能的需求描述是这样的:

  • 在用户下单以后,经过下单流程进入付款功能;
  • 通过用户档案获得用户名称、地址等信息;
  • 记录商品及其数量,并汇总付款金额;
  • 保存订单;
  • 通过远程调用支付接口进行支付。

通过业务场景分析及领域对象关系,可以构建如下领域模型图:

Drawing 2.png

第一次迭代:商品折扣的需求变更

现在我们要在这个模型的基础上增加折扣功能,并且还要分为限时折扣、限量折扣、某类商品的折扣等不同类型。

当用户提出一个需求变更时,为了实现这个变更而修改软件的成本越低,那么软件的设计质量就越高。

单一职责原则要求我们在维护软件的过程中需要不断地进行整理,将软件变化同一个原因的代码放在一起,将软件变化不同原因的代码分开放。

首先要分析付款、折扣的相互关系

  • 当“付款”发生变更时,“折扣”是不是一定要变?
  • 当“折扣”发生变更时,“付款”是不是一定要变?
  • 当“限时折扣”发生变更的时候,“限量折扣”是不是一定要变?
  • 当“限量折扣”发生变更的时候,“某类商品的折扣”是不是一定要变?

分析发现付款和折扣是软件变化的两个不同原因,应当将折扣逻辑单独提取出来,做一个接口。不同的折扣逻辑发生变更时也是互不影响,因此应该分开成独立类,可以通过策略模式实现。

Drawing 10.png

第二次迭代:VIP 会员的需求变更

增加 VIP 会员功能:

  • 对不同类型的 VIP 会员(金卡会员、银卡会员)进行不同的折扣;
  • 在支付时,为 VIP 会员发放福利(积分、返券等);
  • VIP 会员可以享受某些特权。

先回到领域模型,分析“用户”与“VIP 会员”的关系,“付款”与“VIP 会员”的关系:

  • “用户”发生变更时,“VIP 会员”是否要变?
  • “VIP 会员”发生变更时,“用户”是否要变?

通过分析发现,“用户”与“VIP 会员”是两个完全不同的事物。

  • “用户”要做的是用户的注册、变更、注销等操作;
  • “VIP 会员”要做的是会员折扣、会员福利与会员特权;
  • 而“付款”与“VIP 会员”的关系是在付款的过程中去调用会员折扣、会员福利与会员特权。

通过以上的分析,我们做出了以下版本的领域模型:

Drawing 12.png

第三次迭代:增加更多的支付方式

在领域模型中分析“付款”与“支付方式”之间的关系,发现它们也是软件变化不同的原因。

Drawing 14.png

而在设计实现时,因为要与各个第三方的支付系统对接,也就是要与外部系统对接。为了使第三方的外部系统的变更对我们的影响最小化,在它们中间果断加入了“适配器模式”,设计如下:

Drawing 16.png

通过加入适配器模式,订单 Service 在进行支付时调用的不再是外部的支付接口,而是“支付方式”接口,与外部系统解耦。只要保证“支付方式”接口是稳定的,那么订单 Service 就是稳定的。比如:

  • 当支付宝支付接口发生变更时,影响的只限于支付宝 Adapter;
  • 当微信支付接口发生变更时,影响的只限于微信支付 Adapter;
  • 当要增加一个新的支付方式时,只需要再写一个新的 Adapter。

小结

拿到需求时先回归领域模型,结合业务关系进行分析,看是否是导致软件变化的相同原因。【深度了解业务】

软件设计注重单一职责原则,在维护软件的过程中需要不断地进行整理,将软件变化同一个原因的代码放在一起,将软件变化不同原因的代码分开放。【解耦】

为了实现这个变更而修改软件的成本越低,那么软件的设计质量就越高。

DDD 中的一些解耦思想

微服务之间解耦

  1. 界限上下文实现不同业务领域边界的微服务物理边界的解耦

    • 应用部署在不同的进程、服务器、机房
    • 数据库
  2. 通过适当的数据冗余设计,如值对象的业务快照设计,实现了跨微服务不同聚合之间的数据解耦

    • 操作时存储操作人用户姓名,查询时则无需再调用用户服务查询用户信息。
  3. 通过领域事件和消息中间件,以数据最终一致性的策略,实现了微服务之间的异步调用和服务解耦

    • 通过异步消息可以降低接口响应时间,提高系统吞吐量
    • 业务逻辑解耦
    • 适用于近实时的场景

微服务内解耦

  1. 微服务代码目录通过聚合目录和分层目录代码边界,实现不同职责代码边界的解耦,有利于微服务架构的演进时代码的组合和拆分
  2. 微服务内通过分层和不同层的职责边界定义,实现微服务内层职责和代码的解耦
  3. 用户接口层通过 facade 接口和数据组装适配,实现了微服务核心业务逻辑与前端应用的解耦
  4. 应用层通过采用CQRS(Command Query Responsibility Segregation,命令与查询职责分离),把操作和查询分开,不光可以是代码层面,可以在底层存储实现上
    • 可扩展性,读写数据存储可以根据需求扩展
    • 可优化读性能
    • 职责分离
  5. 应用服务通过对不同聚合领域服务的组合和编排,实现了同一个微服务内不同聚合的解耦
  6. 聚合实现了微服务内不同聚合之间逻辑边界的解耦
  7. 聚合之间通过聚合根 ID 引用,而不是对象引用方式,完成不同聚合领域对象之间的访问,实现了聚合之间不同领域对象的解耦
  8. 微服务内聚合之间通过事件总线,以数据最终一致性的策略,实现了聚合之间服务同步调用的解耦
  9. 仓储模式通过依赖倒置策略,实现了核心逻辑领域与基础资源处理逻辑的解耦
    • 业务模型与领域模型代码映射,与底层数据库设计无关
    • 核心逻辑层与持久层技术框架解耦,基础层可以采用不同的技术框架或存储数据库

webp-20230213140313189

结尾

让团队参与分析业务,深入理解业务,打造可以持续演进的业务模型。

业务模型可视化,业务模型和代码模型是映射关系,让代码更易读易维护。

业务模型中的领域名词命名和规则是团队通用语言。

0

评论区