DDD实战之八:冲刺 1 战术之聚合设计(上)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: DDD实战之八:冲刺 1 战术之聚合设计(上)

本篇开始我们对“群买菜”首个冲刺的战术设计进行描述。上篇中,我们已经识别了首个冲刺的 14 个业务用例和 23 个服务契约的识别,并分别给出了相应的业务用例规约和服务契约设计。下面我们分两篇来分别完成:1)按照 14 个业务用例规约完成聚合设计;2)按照 23 个服务契约,在聚合设计的基础上,完成服务设计(含应用服务、领域服务);3)作为首个冲刺,完成必要的战术层面相关技术决策(这一步工作一般只在首个冲刺的时候会做,后面的冲刺可能会有补充完善)。


01首个冲刺的概念模型与聚合划分



本篇就先完成第一个工作:基于 14 个业务用例规约完成聚合设计。对于每个上下文来说,其实我们按照如下的 4 步走的“快速建模法”来完成聚合设计:

1. 名词建模。这一步其实就是查看该上下文的所有业务用例,从其中识别出所有的“名词”,包括那些带定语的名词,并初步建立这些名词之间的关联关系。

2. 动词建模。词建模的主要目的,是为了发现“时标对象”。时标对象我们可以这样来理解:它是用来记录在某些关键时刻涉及到管理责任、法律纠纷或财务风险的“过程性记录”。这种记录的真正作用,并不是业务本身所需要的,而更多是从企业管理角度来考虑的。下图展示了“时标对象”的概念定义和识别方法(绿勾即为时标对象,而红叉则不是):

image.png


3. 归纳抽象。在完成了名词建模、动词时标对象识别后,即可以对对象模型进行抽象归纳,并识别出哪些是值对象、哪些是实体对象。这一般包括这些工作:

  • 通过合并同类项,主要是那些定语修饰的不同名词、其实是一个对象类的情况(如:配送地址、家庭地址等,这种属于定语引起的值的差异);
  • 通过定语识别出新的对象,主要是那些定语修饰的不同名词、其实是不同类型的情况(如:订单状态、商品状态);
  • 去掉一些没有必要存在的对象,比如:没有业务意义的名词、其实可以使用语言基本类型的名词等;
  • 区分值对象、实体对象。按照一个基本的原则来识别,即:是否对象的所有属性相同,但仍然可被认为是不同的对象,这种情况必须要有标识 ID 才能区分不同。
  • 确定实体对象之间的关系,包括:泛化、关联、依赖。泛化是父类子类之间的关系;关联是对象的属性中引用另一个对象,又包括合成关系(A 由 B 合成,表示 B 为 A 的组成部分,并且 B 存亡依赖于 A 的存亡,如学校和班级的关系)、聚合关系(A 由 B 聚合,表示 B 为 A 的组成部分,但 B 存亡并不依赖于 A 的存亡,如班级和学生的关系)、普通关联(即 A、B 之间的普通属性引用关系,允许 1 对 1、1 对多、多对多);依赖是方法出入参引用到另一个对象。


一般来说,对象模型的建立,采用“多比少好”的基本原则。

4. 划分聚合。将一个上下文中的多个实体对象进行聚合划分。一般来说,我们本着“小聚合”的原则,区分聚合的唯一判定规则:该实体对象是否存在从用户角度被直接查询和处理的必要。例如:订单里面的订单项,用户就没必要跳过订单、而直接查询订单项的必要,这种情况下“订单项”就作为“订单”聚合的内容,而不需要单独作为聚合存在。之所以这么做,是因为按照我们的菱形架构,对于数据资源类的端口采用“资源库”接口来实现,而一个聚合对应一个资源库。所以,如果不需要为某个实体对象单独开发“资源库”端口(及对应的适配器类),就没必要将其作为独立的聚合。


02鉴权上下文



1名词建模


根据用例“登录系统”规约查找名词:微信 openid、微信授权信息、授权记录、用户 ID、有可管理店铺标记、有可管理接龙标记、店铺 ID、位置、距离。


2动词建模(时标对象)


重大时刻:登录系统

可能的过程性记录:登录日志

是否关联到管理责任、法律纠纷、财务风险:考虑到用户微信可能被盗用,而且“群买菜”是个双边开放平台,允许任何人注册店铺和销售商品,为了规避法律和财务风险,故有必要将登录日志保存下来。为此,识别出“登录日志”这一时标对象。


3归纳抽象


根据如上识别的所有对象,我们绘制概念模型图如下:

image.png

对上图进行相应的归纳抽象后,我们发现:

“授权记录”其实就是在我们“群买菜”系统的“用户”,其实质是微信用户在授权登录后在“群买菜”系统的一对一映射。这样“用户 ID”其实就是“用户”对象的标识。所以,“用户 ID”应该是值对象。

“店铺”其实是“店铺上下文”的实体对象,授权上下文只关心“店铺 ID”,由于跨上下文,故只需要作为“用户”实体对象的“计算性属性”(最近一次浏览的店铺 ID、或距离最近店铺 ID),且使用基本类型 String 即可。

“微信 openid”应该作为“用户”实体对象的属性。考虑到它实际上是一种特定平台、特定格式、特定含义的字符串,故设计为值对象。

“登录令牌”是依附于“用户”存在的,并且每次登录后都被更新掉,没有自身标识 ID 存在的必要,故也作为值对象存在。

“微信授权信息”是个“瞬态信息”,可以作为值对象存在。

“位置”。由于我们并不是一个物流或地图类应用,不需要对位置进行精确的匹配,所以作为值对象。并且,在我们的“授权上下文”中,其应该是用户对象在某个时刻的一个计算属性(根据手机定位计算)。

“距离”。这是从经纬度计算出来的一个整数或浮点数(视采用的计算单位而定),但它有特定的业务含义,故设计为值对象。同时,它是根据位置进行计算的,所以它和“位置”值对象之间是一种方法调用上的“依赖”关系(“距离”对象会使用“位置”对象来构建自身)。

“有可管理店铺标记”、“有可管理接龙标记”,这明显是两个计算属性(根据该用户是否被店铺创建人授权、是否创建接龙等计算),可以作为“用户”实体对象的属性存在。考虑到这类标记性属性,会随着“群买菜”系统业务逻辑的演变、以及前端展示需求的变化等需要,我们可以设计一个“用户状态”值对象类。

“登录日志”应该是实体对象,且“用户”和“登录日志”之间应该是“合成”关系(后者是因为前者存在而存在的)。

根据上面的归纳抽象,再考虑用英文表达对象名称,我们修改概念模型图如下(值对象用阴影表示,箭头表示单向关联,实心菱形表示合成关系,空心菱形表示聚合关系,无箭头实线表示双向关联)

image.png

张逸按:我通常将以下两个步骤——分辨实体和值对象;确定实体之间的关系——放到领域设计建模的过程中,在快速建模过程中,不建议介入软件设计的要素,我希望由领域专家(可以不懂软件设计)来主导这一过程。」

4划分聚合


本上下文只有两个实体对象:用户、登录日志。唯一要回答的问题是:“登录日志”是作为“用户聚合”的内容、还是独立聚合存在?这取决于业务上有没有不需要通过“用户”实体对象而直接访问“登录日志”的需求场景。从实际需求来说,“登录日志”是动词时标对象,我们前面做分析时已经意识到记录它的目的是为了方便以后的财务、法律风险核查,也就是说可能会开发针对“群买菜”平台后端运营的相关功能,而这些功能是可能是直接查询某个时间段、或满足某登录地理位置范围等审计条件下的“登录日志”,而并不需要通过“用户”对象来访问它。为此,我们将聚合划分如下图(图中<<AR>>标记表示是“聚合根”):

image.png


上图中,需要说明的是:考虑到“位置”和“距离”与业务的完全无关性,建议将“Location”和“Distance”两个类放到“共享内核”上下文中,不归属到某个聚合。


03订单上下文




1名词建模


根据各业务用例规约查找名词如下表:

image.png

我们将上表的所有名词对象进行汇总,得出如图所示的概念模型:

image.png


2动词建模(时标对象)


对订单上下文各业务用例的时标对象分析如下表:

image.png


总结起来,对订单上下文动词建模新增的对象有:“微信支付结果”、“订单操作日志”。调整后的对象模型如下图:

image.png


3归纳抽象


现在我们来对这个初步的对象概念模型进行归纳抽象,首先我对某些对象的存在必要性进行分析如下:

“商品”、“商品有货状态”、“售罄商品”都属于“商品上下文”的内容,我们在这里作为考虑。

“订单生效事件”、“订单确认事件”属于菱形架构中“南向网关”部分,不属于核心领域,可以不作为对象模型考虑。

“订单状态通知订阅”、“订单状态通知消息”按照上下文职责划分属于“平台集成上下文”,也不在这里考虑。

“订单列表”其实就是“订单”对象的一种 List,且仅用于前端界面查询显示,不需要作为对象模型考虑。

“购物车商品列表”其实是购车中保存的商品信息、下单份数、计量数量(比如:胡萝卜 0.5 斤一份,下单了 3 份就是 1.5 斤)、下单金额小计等信息,故改名为“购物车商品行”。

修改后的对象概念模型如下图:

image.png

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
3月前
|
架构师 开发者
【悬念揭秘】DDD:那片隐藏在软件深处的业务乐土——.NET项目如何借力领域驱动设计,让复杂业务逻辑迎刃而解?
【8月更文挑战第28天】领域驱动设计(DDD)在.NET项目中的应用聚焦于将业务领域知识与软件开发紧密结合,通过构建清晰的领域模型管理复杂业务逻辑。DDD的核心概念包括限界上下文、聚合、实体等,确保模型与实现的统一。在.NET中,通过CQRS和事件源等模式提高系统响应性和可扩展性,实现业务事件驱动的解耦与协作。DDD不仅是一种设计方法,更是要求开发者深入理解业务的文化,助力.NET项目应对复杂挑战,实现业务与技术的融合。
64 6
|
6月前
|
敏捷开发 监控 架构师
【领域驱动设计专题】一文带领你透视DDD领域驱动模型的本质和设计原理分析指南(构建领域知识)
【领域驱动设计专题】一文带领你透视DDD领域驱动模型的本质和设计原理分析指南(构建领域知识)
186 0
|
架构师 算法 测试技术
小团队也能做DDD-中篇
小团队也能做DDD-中篇
231 0
|
前端开发 小程序 Java
DDD实战之八:冲刺 1 战术之聚合设计(下)
DDD实战之八:冲刺 1 战术之聚合设计(下)
DDD实战之八:冲刺 1 战术之聚合设计(下)
|
敏捷开发 消息中间件 前端开发
DDD实战之七: 战术设计、整体流程与首次冲刺
DDD实战之七: 战术设计、整体流程与首次冲刺
DDD实战之七: 战术设计、整体流程与首次冲刺
|
消息中间件 运维 前端开发
DDD实战之六:战略设计之技术决策
DDD实战之六:战略设计之技术决策
DDD实战之六:战略设计之技术决策
|
架构师
《架构师修炼之道》第八章--建立模型,化繁为简
项目进入了开发阶段,我们发现团队成员描述同一架构元素时使用的词汇各不相同。我们的设计决策表面上取得了一致意见,但大家实际各有各的理解。
349 0
《架构师修炼之道》第八章--建立模型,化繁为简
|
架构师 前端开发 测试技术
为了成为一名架构师必须稳扎稳打,软件架构设计的模块划分
之前,我们在开发的时候总是惯性思维的以某张业务表的维度进行三层结构的功能开发,没有去思考他们功能模块间的关系,只是为了完成目标而进行开发。