领域驱动设计:软件核心复杂性应对之道
作者:[美] 埃里克 埃文斯(Eric Evans)
开发者很容易专注于技术领域,而忽略了业务领域的核心。比如在业务设计(战略设计)过程中,不断代入各种技术,某某模块的数据库如何设计,某模块要使用 xxx 技术,并且带到业务讨论中,这不仅会给自己对业务理解带来局限,并且也会干扰业务专家的思路。
开发人员正确的做法是,先抛弃技术的思路,先深入理解业务需求,在深入且明确业务后,再来设计技术领域(战术设计)的内容。
明确一个团队只在一个限界上下文中工作,团队必须自己控制源码、数据库并定义官方接口,所有团队接入必须通过这些接口才能调用限界上下文
所以DDD是不是中台概念的出生地?
DDD 更倾向于在协作沟通中深入对业务模型进行理解,而不是通过文档
区分核心域、子域,看起来像是软件工程中的命名空间?
异步消息机制中,发布者有 2 种提供数据的思路,这两种思路需要权衡当前场景的适用情况:
增强事件:比如需要订阅者自治,一次性将全部可能的数据都推送出来
反向查询:比如有安全需求,不能一次性将全部数据提供出去
当然,这两种也可以酌情混合使用
限界上下文间的映射关系类型:
合作关系
共享内核
客户-供应商
追随者:强调服务使用者的被动适应性
防腐层(Anticorruption Layer)
开放式主机服务(Open Host Service):强调服务提供者的主动性
已发布语言(Published Language)
各行其道
我理解的DDD 中的“限界上下文”和“映射关系”:
限界上下文理解为代码中的命名空间,一个命名空间可以看作是一个业务模型领域的抽象
映射关系表示胃不同限界上下文的依赖关系,粗暴点说,就是看谁占据协作中的主动权、话语权
异步消息机制可以很好地解决集体火车事故,在实时性要求不高的情况下,使用异步消息机制是很好的选择。
集体火车事故:A 使用同步请求 B,B 使用同步请求 C,一旦 B 或则 C 崩了,那 A、B、C 都崩了
值对象,一种类似于值类型的对象。
它关注的是对象属性的值是否一致,而不在乎内存地址引用是否相同
举例:带有货币单位和金额的对象,比如一万人民币,它就不在乎保存这两个属性的对象是否地址相同。
在聚合边界内保护业务规则不变性:通过使用聚合,来实现和维护业务规则的要求
聚合要设计得小巧:聚合作为业务的最小模型,小巧既可以让实现变得简单,同时内存占用更小、效率更高,从而提高事务成功率;设计时要注意使用 SRP(单一职责原则)
只能通过标识符引用其他聚合:进一步提高性能,并且解耦聚合间的持久化类型依赖
利用最终一致性更新其他聚合:不要求实时更新两个聚合的数据,关注的是最终一致性(区别于实时一致性)
聚合间的最终一致性是什么?
最终一致性不要求系统在完成操作后,立刻达到全局一致性。但会保证在一段时间后,达成全局一致性
聚合间的操作为什么使用的是最终一致性?
与最终一致性相对的,是实时一致性。如果想在跨聚合的操作中实现实时一致性,会大大增加系统的复杂性和耦合度。
实体
实体是业务逻辑的具象化描述,它代表了有唯一标识的业务概念。一个实体包含了这个业务的状态和行为,所以它是一个对业务的动态描述。
聚合和实体的关系
聚合的概念,比实体更大。聚合包含一个根实体,并且在聚合中,它是一组相关对象的集合,并且被作为修改数据的单元。
根实体:作为聚合对外交互的唯一通道,用于维护聚合内的规则和一致性。
实体作为聚合的一部分:包含一个或多个的实体和值对象,它们共同工作,维护一套业务逻辑或业务规则。
聚合保障了具体业务的一致性和原子性,而实体为这些业务操作提供了具体的实现。
避免在设计聚合时过度抽象,过度抽象会导致:
模型和领域专家心智模型不一致
形成错误的抽象,殃及 UI
在面对具体问题时,可能需要使用通用方法解决具体问题,导致形成复杂的层次结构
如果未来的需求和现有模型不一致,可能会带来更大的成本问题(改造、以及之前花费比原有更多的时间去做的抽象)
在使用增强领域事件时,注意不要传递太多的属性,否则可能会对消费者产生误导。
{
data: {
ceName: "xxx",
payload: {
ceName: "xxx",
}
}
}
假设在外层有个 ceName,payload 内层还有一个 ceName,业务上,它代表同一个意思,并且:
- A 状态下,ceName 为空,payload.ceName 为正确值
- B 状态下,ceName 为正确值,payload.ceName 为空
- C 状态下,ceName 为正确值,payload.ceName 为错误值
这样不仅有冗余职责的同名属性,并且在不同位置来回横跳,还有可能出现错误值,这样接入方会十分混乱,并且出现异常。
触发领域事件的动作:
执行命令(通过 UI 触发,事件可能会被拒绝)
其他原因:特殊的时间节点(既成事实不可拒绝执行事件)
什么是事件风暴?
通过一系列方法,对业务进行分析建模,并形成基于时间流的命令/领域事件图:
- 按照时间线,从左到右罗列出关联的领域事件
- 将触发领域事件的命令,在每个领域事件左侧写上,形成一一对应的关系
- 基于所有的命令,分析如果一个命令需要多个领域事件,则抽象成一个类似于“事件组”的卡片,将“事件组”与一个命令关联起来,否则直接一对一连线关联即可。并且如果有非常重要的领域事件干系人,可以把干系人写在笔上
- 为领域事件划定边界,形成限界上下文,并且在限界上下文间的交互中,使用领域事件进行关联
- 识别用户操作所需要的视图,如必要则关联上干系人
有效建模的要素
- 模型和现实绑定
- 建立了一种基于模型的语言
- 开发一个蕴含丰富知识的模型
- 提炼模型
- 头脑风暴和实验
好的 service 的特点:
- 是无状态的
- 它的处理逻辑不应该是实体或值对象能包含的部分,应该是独立的一块公用逻辑
- 需要基于其他元素定义
Factory 承担领域对象生命周期中的开始部分,而 Repository 负责承担领域对象生命周期的中间和结束部分。
|----开始----|----中间----|----结束----|
|--Factory--|-------Repository--------|