designproject学习 DDD 读书笔记
Posted 踢踢vip
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了designproject学习 DDD 读书笔记相关的知识,希望对你有一定的参考价值。
DDD
原文链接:Structure your microservice using a hexagonal architecture by Fredrik Lindgren
原文链接:What is DDD - Eric Evans
原文链接:DDD and Microservices: At Last, Some Boundaries!
文章目录
第一部分:六边形架构
原文链接:Structure your microservice using a hexagonal architecture by Fredrik Lindgren
Hexagonal Architecture 又名:端口和适配器模式
是一种对测试友好的服务架构模式
定义
allow an application to equally be driven by users, programs, automated test or scripts, and to be developed and tested in isolation from its eventual run-time devices and databases
允许 application 研发过程由,用户驱动、旧有程序驱动、自动化测试驱动或脚本驱动
开发和测试与最终运行时设备和数据库隔离
为什么使用六边形架构
-
提出外部与内部的概念,对比传统分层架构
- 传统的分层架构
- 六边形架构
- 传统的分层架构
-
提出对外暴露多个端口,区别与传统分层架构的向上提供接口,向右提供接口
- 多个端口,为嘛叫六边形架构?因为懒(゜ー゜)。对比分层架构的矩形,画个六边形最省事
六边形架构小实栗
需求
浏览器 ——> 报名参加一项课程 ------> 检查课程报名条件,存储报名信息
浏览器 ——> 报名参加一项课程 ------> 发送报名确认邮件,发送通知给授课老师
使用传统的分层架构
POST 请求 ——》 报名服务 ——》通知服务
遇到问题:应用程序在一直变化
考察报名服务 参见:API Design:业务一直在变化
-
业务需求在变化
-
使用的技术在变化
-
要求使用协作 sheet 存储数据
-
协作 sheet 的版本在升级
-
略
-
-
尽管使用的技术在一直变化,但是核心业务中的一些基础概念(basic business concepts)一直沿用下来
有人说:六边形架构,将常规MVC三层架构反转,以业务为主导,自顶向下
这里“业务”指的是能一直沿用下去的,经过提炼的,核心业务中的基础概念。
遇到问题:自动化测试不好使了
-
每一次提交都需要做测试,而且是集成测试
-
新技术协作 sheet 不便于自动化测试验证
-
我们可以内网 模拟一套 协作 sheet 的 API 来进行测试,还不能保证准确性
-
所以,我们引入了端口和适配器
驱动方(Driver Side) | 应用程序核心(Application Core) | 被驱动方(Driven Side) |
---|---|---|
主动请求系统的一方为驱动者 | 应用程序核心不需要知道实现细节 | 被系统调用的一方为被驱动着 |
所有的业务逻辑必须在核心中定义 | 非核心功能(secondary) | |
核心使用端口(java 中叫接口中的方法)(ports)主动定义系统边界。具体的实现通过端口和核心的双向交互 | ||
具体的实现使用适配器(Adapters)和核心做消息转换 | ||
驱动方的适配器(Adapters)通过驱动端口(driver ports)调用核心 | ||
核心通过驱动端口(driver ports)调用被驱动方。被驱动方s实现驱动端口(driver ports) | ||
发出 event | cqrs | xxx |
六边形架构——端口(ports)
端口分类
-
交互端口(Purpose of Interaction)
-
一组和(应用程序)外部“用户”交互的端口(接口)
-
关键目标是交互,具有技术无关性
问题:给学生发消息,与给老师发消息是否需要分开来?
这是一个 trade-off,在本系统中,提供一个高层抽象,发送 Notification
-
-
应用逻辑端口
端口不应该和技术相关,而应该和应用程序逻辑相关
接口使用业务命名,而不是 saveXxx 之类的
-
端口属于某个应该程序
-
这些端口定义了该应用的边界
-
整体架构
ports ================ components =================== adapters
自动化测试解决方案
-
使用 生产-测试 双重适配器,用以测试业务逻辑
-
Mock 框架,模拟外部系统
-
通过外部系统 adapter 测试外部系统,外部系统的集中测试从业务逻辑中独立出来
-
最后做一些交付测试
设计原则
-
隔离外部依赖关系
-
从简单开始,如果需要,再添加额外的东西,例如单独的模块
-
简单的 API,宽而浅的 API,不要使用错综复杂的调用层次很深的 API。
-
尽量将 ports 封装到单个接口中,且接口中的方法全部是需要 exposed 的方法
-
避免接口里返回接口
-
-
ports 中的参数使用不可变对象
第二部分:DDD 基础——是什么?为什么?怎么做?
最有效的设计软件核心逻辑的方法是什么?Models 或者是获得 Models 的方法
直接举个栗子
考虑集装箱运输货物的物流问题。集装箱装上船飘洋过海,到达港口,使用转运大卡车,搬到火车站或者其他航线
假设我们要从天津运到上海,我们走老秦开挖的“京杭大运河”到杭州,然后换火车送到上海
天津 ==== 京杭大运河 ====> 杭州 ====== 火车 =====> 上海
我们还不能开始设计 Model 和 API
- 首先考虑问题域的其他旧系统,考虑系统集成 context-Mapping
- 其次……
先 look look 高层架构
名称 | 属性 | 行为 |
---|---|---|
货物 | id,始发地,重点,重量 | x |
运输服务 | 货物ID,航线ID,上货地点,卸货地点,船ID,时间 | x |
-
问题1:从软件的实现者,开发人员的角度来说
- 可读性,可扩展性…
- 运输服务太大了,里头逻辑太多,太复杂
- 🤦♂️ 因为有一条老的软件原则,不要使用 update 数据库 state 来实现业务逻辑 🤷♂️
- 你这运输表得多少状态字段啊……
-
问题2:在非开发人员,需求定义角度来说,整体的领域模式 Model 也有问题
- 同物流专家的角度探讨一下,基本流程,栗如把这个存储到数据库
- 天津 ==== 京杭大运河 ====> 杭州港 = ??? => 杭州火车站 ==== 火车 ====> 上海
- 杭州港到杭州火车站好像挺远的………………
- ━┳━ ━┳━ 怎么和专家解释呢?这时候我们需要一种共通语言(DDD 的重要组成部分)
所以,我们的 application core ,缺少哪些基本概念(concept)?
-
一种方案
名称 属性 行为 运输服务 货物ID,…,出发时间,到达时间 x 站点 是否始发站,是否终点站 -
第二种方案
我们还需要对比其他方案,找到最佳方案。做软件前多想想替代方案
名称 属性 行为 运输服务 货物ID,…,出发时间,到达时间 x 脚程上货地点,卸货地点x航线 上货地点,卸货地点 x -
站点和航线除了名字,好像没有什么区别嘛!
-
将脚程 替换 成 航线
-
斟酌词汇,获得更好的设计
-
-
一种更好的方案
运输服务 -------> 行程 --------》站点
名称 属性 行为 运输服务 货物ID,…,出发时间,到达时间 x 行程 x x 站点 上货地点,卸货地点 x -
当需求变异,不要犹豫不前,这是完善领域模型的好机会
-
当需求编译,不要吹毛求疵,不要使用错误的 Model,换下一个
-
文档?我们只得到了三个词汇:运输服务,行程,站点
选择一个 Model 方案
使用 UML 背后的 Model,背后的基础概念(concept) 而不是 UML 本身
不同的 Model 适用不同的目标问题,所以这个问题本身就有问题
名称 | 属性 | 哪个更好? |
---|---|---|
航线 | 起点,终点,运输方式 | ✔ |
站点 | 位置,上/卸货地 | ✖ |
站在不同的角度,对一个问题有不同的看法
-
使用一种最适用于目标问题的 Model 来表述
-
能最清晰的表述目标问题
-
栗如地图:你是要用地图导航?还是用地图测绘?不同的问题适用不同的地图
一个 Model 由一系列 Statements 组成
-
xxxx(abstraction)
-
xxxx
-
xxxx(assertion)
Modle 的定义
-
Domain 是信息和活动的集合(一个界定)
-
Model 是 Domain 的某个方面(aspect)的抽象,
-
Model 细化了 Domain
-
Model 建立在一定的假设的前提下
-
过多考虑现实使人郁闷(Realism is a distraction),不一定有利于目标问题
Model 应该缩小关注点
-
建议从不同的方面(aspect)建立多个 model 来,处理一些大的 Domain
-
不要用一个 Model 代表所有
哪个 Model 更有用?用这个 Model 来解决 xxx 问题?
名称 | 属性 | 对于当前的问题,哪个更好 |
---|---|---|
航线 | 起点,终点,运输方式 | ✔ 我们只需要让上一个航线终点对应到这个航线的起点就可以,所以这个 Model 更方便 |
站点 | 位置,上/卸货地 | ✖ |
对于当前问题:
天津 ==== 京杭大运河====> 杭州港 = ??? => 杭州火车站 ==== 火车 ====> 上海
所以,最终方案
名称 | 属性 | 行为 |
---|---|---|
货物 | id,始发地,重点,重量 | x |
运输服务 | 货物ID,…,出发时间,到达时间 | x |
行程 | x | x |
航线 | 起点,终点,运输方式 | x |
-
我们的运输服务,不再直接操作数据库修改状态 ✅
-
我们只需要让上一个航线终点对应到这个航线的起点就能连接航线
-
主要任务是定义一些词汇:货物……
为什么要定义这些 Model
-
因为有的业务非常复杂,有助于理解 Domain
-
能避免,造出比现实更复杂的设计
现在业务扩展了,我们重新需要站点,用于到站计费啥的
-
修改原有系统,替换航线为站点
-
使用一个新的子系统,引入站点的概念
-
使用航线的起点代表站点
怎么做?参考上文
- 如果 model 不再适用与目标问题,不管 Model 设计的多好,弃之食草
第三部分:DDD与微服务
原文链接:DDD and Microservices: At Last, Some Boundaries!
MicroService
-
自主的团队,隔离的实现
-
管理大型团队带来的管理问题(acknowledge the rough and tumble of enterprises)
-
各个部分业务编码水平不一致(cattle not pets)
-
带来领域驱动的机遇
-
等等其他……
两个服务之间的交互如何定义?
如何做服务集成?
-
在交流中的上下文:语义环境,决定其含义
-
在软件中的限界上下文:
-
为了简化计算机间不同上下文的整合映射
-
指定一个模块内,特定 Model 的含义一致
-
有时候,划分模块很复杂,程序一堆乱麻
-
交互遇到的问题:上下文映射(Context-Map)越来越复杂
在 DDD 中,我们引入了一种新技术 Context-Map,上下文映射,假设一个没有边界的系统如下:
-
A ---- partner ---- B
- 意味着 A 和 B 之间存在一个 translator ,使得 A 和 B 之间能相互理解
-
假设现在 A 和 B 交互,B 和 A 交互,A 和 C 交互
-
C 是 A 的下位系统
-
C – conforming --> A <–> B:C 对 A 具有一致性(conforming)
-
也就是说 C 中的 Context (使用相同的消息定义,或者说是相同的语言)需要保持和 A 一致,C 为了和 A 集成,放弃一部分自主技术选型的机会
-
人们一般会这么处理下位系统 C
-
-
假设 D 也需要 和 A 交互,D --> A <–>,且 D 不愿意适用和 A 一致的上下文
- D 与 A 之间需要定义一个更复杂的 translator
-
E --> A ,A 需要从 E 获得数据
- 这意味着,尽管是从 E 指向 A,但是是数据流,A 依旧是驱动方,E 是被驱动方
- 这时候 A 不能很好的完成工作,因为现在 A 具有很高的耦合(C,D,E都和 A 交互),而 A 本身的 Model 是为了处理复杂逻辑而设计的,而不是处理数据转换而设计的
-
现在 D 也需要订阅 E 发出的消息
-
F 需要订阅 B ,然后 C 确认从 B 过来的消息
- 这时,C 需要确认 F 和 B,这是不可行的。
交互遇到的问题:我们总是有不同的 Model
-
旧系统
-
不同的 team 会定义不同的 model
交互遇到的问题:应用集成是,不同的系统业务不一致
-
Model 必须小清晰
-
Model 必须有清晰的定义
-
定义清晰的上下文
-
需要有简单的断言
- 栗如,不存在没有订单的客户,但不是所有的系统都有这样的假设
-
所以我们需要边界来支持业务断言
-
一种解决方案
让 A、C、D、E 都和新引入的 I 交互。I = 一个内部交换上下文(interchange context)
不改变原有的交互方式,只改变得到消息的方式。
内部交换上下文
-
为了更好的执行,我们引入了一个 Domain Language
-
以服务接口/消息的形式表示
-
不同于服务内部的对象/功能
-
防止限界上下文的早期-扭曲/冻结
-
当我们有许多服务时,提供全局性的理解
-
和企业级集成不同,我们会有不止一个交换上下文(为边界内频繁交互的服务做交换上下文)
为什么不使用逻辑边界
-
为啥不做系统的逻辑划分?(模块划分)
-
因为我们做逻辑划分做了几十年了,也没有看到效果。
-
逻辑边界不是很清晰,不会有明确的物理边界
-
规范会所有东西不现实
-
如果是传统的单一项目,这是可行的
-
-
实际经验表明,大部分的项目,逻辑划分无法经受风吹雨打
用别的方式来划分边界
-
DDD 提出了具体的(concrete boundaries)边界,微服务恰好提供这些特性
-
服务的激增重现了一些旧问题
- 当太多的服务进行交互时,很容易出现 “意大利面代码”(一团乱麻)
-
有助于构建高内聚的微服务集群
《领域驱动设计》读书笔记
最近在看DDD的经典的一本书《领域驱动设计-软件核心复杂性应对之道》,正好也在做一个权限模块的设计,然后尝试着用DDD来做业务的梳理。
DDD在软件行业应用很广泛,技术大会上也经常有嘉宾来分享各自的实践。从业务梳理,架构设计,开发,重构,整个软件开发过程,都有重要的指导意义。
全书介绍了领域驱动设计的系统化方法,还有作者本身的见解及经验,展现了一些可扩展的设计最佳实践、已验证过的技术以及便于应对复杂领域的软件项目开发的基本原则。但是因为翻译和例子(举了航海及金融的业务,不好理解)不太好,导致看的过程会觉得比较晦涩。整理笔记的时候,摘抄了书中一些核心内容,也加入一些自己的理解。
全书分成了四个部分:
运用领域模型:介绍相关的概念及作用
模型驱动设计的构造块:实现模型驱动设计
通过重构来加深理解:重构对领域模型的影响
战略设计:从战术上升到战略层设计
运用领域模型
这部分主要讲领域模型相关的概念,模型在领域设计中的作用。
模型在领域驱动设计中的作用:
模型和设计的核心互相影响,可以基于模型的理解来解释代码
模型是团队所有成员所使用的通用语言的中枢,可以借助自然语言对模型进行精化
模型是浓缩的知识,对业务有足够的抽象和提炼
软件的核心是为用户解决领域相关的问题的能力。技术不能脱离业务。
这个部分介绍了四章:
消化知识:需要理解业务,并且持续理解
交流和语言的应用:统一团队的语言,沟通和文档的作用
绑定模型与实现:建模和实现过程需要注意事项
消化知识
消化知识即理解业务。
有效建模的因素有以下:
模型和实现绑定
建立了一种基于模型的语言
开发了一个蕴含丰富知识的模型
提炼模型
头脑风暴和实验
我们在权限模块的时候也有类似的活动,如头脑风暴。核心人员做在一起来讨论整个系统的核心业务,统一大家对权限的认识,基于UML图来画领域模型,把技术细节忽略掉,专注于核心的业务。
交流与语言的应用
这章主要讲的是技术人员要与领域专家保持沟通,提到了需要注意下面几点:
一个团队,一个语言(统一团队内部的语言,对各个实体的理解及表达是否一致):
领域专家和开发通过讨论可以发现模型中不足,模型的精确性也会促使领域专家发现想法的矛盾之处。
开发和领域专家可以通过模型走查场景
文档与图:
图只包含对象模型的重要概念,应该避免包罗万象的对象模型图。
设计的重要细节应该在代码中体现出来。
图是一种手段,可以促进头脑风暴。
模型不是图,图的目的是帮助表达和解释模型
书面设计文档:
文档应作为代码和口头交流的补充
代码作为设计文档也有局限性,会淹没在各种细节中
文档不应该重复表示代码已经明确表达出的内容
文档应该鲜活并保持最新
文档必须深入到各个项目中去
对于文档和图,我们在做的时候觉得是应该有文档的,前期设计时,我们大部分的时候讨论都是基于我们画UML图来进行沟通,来走查我们的业务场景。但是大部分开发人员都不爱写文档,所以我们做的时候也尽量精简文档。
绑定模型及实现*
这章介绍的是则是模型的实现过程可能会出现的一些问题。
两种错误的开发流程:
建立了领域模型,但是不能直接帮助开发可运行的软件。
没有设计,直接累加功能。
模型驱动设计不再将分析模型和程序设计分开,而是寻求一种能够满这两方面需求的单一模型。
软件系统各个部分的设计应该忠实地反映领域模型,以便体现出这二者之间的明确对应关系。我们应该反复检查并修改模型,以便软件可以更加自然地实现模型,即使想让模型反映出更深层次的领域概念也应如此。
从模型中获取用于程序设计和基本职责分配的术语。让程序代码成为模型的表达,代码的改变可能会是模型的改变。
完全依赖模型的实现通常需要支持建模范式的软件开发工具和语言,如面向对象的编程。
设计了领域模型却没有派上用场的原因:
模型的一些意图在其传递过程中丢失了。
模型和程序实现及技术互相影响,而我无法获取这种反馈。
任何参与建模的技术人员,都需要花时间了解代码。任何负责修改代码的人则必须学会用代码来表达模型。
我们之前在做的时候也发生过类似的,前期系统设计一完成,领域模型便没有人关注了,导致后面我们代码实现,跟我们原来设计的领域差异还是比较大的,所以后面需要持续关注代码的实现。
模型驱动设计的构造块
这部分主要讲的是如何来设计领域模型,细微的模型差别和设计决策是如何影响领域驱动设计过程的。
内容包括如下部分:
分离领域:如何做架构设计
各个模型对象的区分:区分实体,值对象,service等
领域对象的生命周期:怎么创建?怎么跟踪?涉及到工厂,仓库等概念。
分离领域
对于架构设计来说,建议把领域模型对应的各个领域对象集中在一层,跟技术框架无关。
分层架构模式:
用户层:负责向用户显示信息和解释用户指令。
应用层:定义软件要完成的任务,并且指挥表达领域概念对象来解决问题。这一层也是与其它系统的应用层进行交互的必要通道。应用层要尽量简单,不包含业务规则或知识,而只为下一层的领域对象协调任务,分配工作,使它们互相协作。
领域层(模型层):负责表达业务概念,业务状态信息以及业务规则。尽管保存业务状态的技术细节是由基础设施层实现的。但是反映业务情况的状态是由本层控制并且使用的。领域层是业务软件的核心。
基础设施层:为上面各层提供通用的技术能力。
将所有与领域模型相关的代码放在一个层中,把它跟其它各层分开。领域对象应该将重点放在如何表达领域模型上,而不需要考虑自己的显示和存储问题,也无需管理应用任务等内容。
除了分层架构,这里还介绍反模式,就是不做分层,在用户界面里实现所有的逻辑,对于某些场景来看,也有它的优势。
软件中所表示的模型
本章主要讨论如何设计和简化关联开始,然后讲了3个模型元素:Entity, Value Object, Service和Module。
简化关系的方法:
规定一个遍历方向:如果不是双向的关联,可以按一个遍历方向进行。
添加一个限定符,以便有效减少多重关联:如一个国家某个时期只能有一个总统,这样可以把多重关系简化成一对一关系。
消除不必要的关联
Entity:主要由标识定义的对象(有唯一的ID来标识它,通过这个ID可以查找到所有的信息, 如人有身份证号),可以是任何事物,只需要满足以下条件:
它在整个生命周期中具有连续性
它的区别并不是由那些对用户非常重要的属性决定。
ValueObject: 如果我们只关心一个模型元素的属性时,这个就是ValueObject。如我用铅笔,具体是哪支铅笔我并不关心。
Service: 以对象形式存在,但是除了执行一些操作之外没有其它意义,如一些manager等类。好的service有3个特征:
与领域概念相关的操作并不是Entity和ValueObject的一个自然组成部分
接口是根据领域模型的其它元素定义的
操作是无状态的。
Module是更粗粒度的划分,Module之间是低耦合,内部是高内聚。
Module提供了两种观察模型的方式:
可以在module中查看细节,而不会被整个模型淹没
观察Module之间的关系,而不考虑其内部细节。
领域对象的生命周期
生命周期的管理的主要挑战有两个:
在整个生命周期中维护完整性
防止模型陷入管理生命周期复杂性造成的困境中
主要介绍了三个内容:
聚合:通过定义清晰的所属关系和边界,并避免混乱,错综复杂的对象关系网来实现模型的内聚。
使用工厂来创建和重建复杂对象和聚合,从而封装它们的内部结构
生命周期的中间和末尾使用了仓库来提供查找和检索持久化对象并封装庞大基础设施的手段。
通过重构来加深理解
重构大家都能理解,我们在做重构的时候是否也会考虑领域的设计?之前没有觉得,看完后觉得,除非是做纯技术性的重构不需要考虑领域模型之外,涉及到的业务的重构一定要跟领域模型一起进行考虑。
要想成功开发出实用的模型,需要注意以下3点:
复杂巧妙的领域模型是可以实现的,也是值得我们去花费力气实现的。
这样的模型不能离开不断的重构,重构需要领域专家和热爱学习领域知识的开发人员参与进来。
要实现并有效运用模型,需要精通设计技巧。
内容包括:
突破:多思考,会找着重构的突破点
将隐式概念转变成显式概念:把隐含的关系在领域模型中显式表现出来。
柔性设计:如何让系统达到柔性设计。
应用分析模式
将设计模式应用于模型
通过重构得到更深层的理解
突破
随着对业务的持续梳理和理解,就能发现深层模型,更符合当前的业务核心,实现突破。
举了银行信贷的例子来解释突破。
华而不实的模型
突破
提炼深层的模型
冷静决策,决定重构
关注根本:
不要试图制造突破
要为突破做好准备
不要犹豫不去做小的改进
将隐式概念转变成显式概念
如何实现深层模型?深层模型之所以强大是因为它包含了领域的核心概念和对象,能够以简单灵活的方式表达出基本的用户活动,问题及解决方案。
深层建模的第一步就是设法在模型中表达出领域的基本概念,随后,在不断消化知识和重构的过程中,实现模型精细化。但是实际上这个过程是从我们识别出某个重要概念并且在模型和设计中把它显式表达出来的那一刻起开始的。
概念挖掘:
倾听语言:看领域专家有没有一些术语能够简洁地表达出复杂的概念
检查不足之处
思考矛盾之处
尝试,再尝试
如何为不太明显的概念建模?
显示的约束:用策略模型来显式约束
将过程建模为领域对象:将规格变成领域对象
规模模式:
规格的应用和实现:按下面的场景分别举了场景的示例
柔性设计
软件的困境:
具有复杂行为的软件缺乏良好的设计时,重构或元素组合会变得很困难。
一旦开发人员不能十分肯定地预知计算的全部含义,就会出现重复
当设计元素都是整块的而无法重新组合的时候,重复就是一种必然的结果
为了使项目能够随着开发工作的进行加速前进,而不会由于它自己的老化停滞不前,设计必须让人们乐于使用,而且易于做出修改,这就是柔性设计。
为了实现柔性设计,有以下几个方法:
释意接口:在命名类和操作时要描述它们的效果和目的,不要暴露它的实现。
SIDE-EFFECT-FREE-FUNCTION:尽可能把程序的逻辑放在函数中,因为函数是只返回结果而不产生副作用。
断言:把可能错误的结果用断言显示写出来。
Conceptual Contour(概念轮廓):把设计元素(操作,接口和类等)分解为内聚的单元。
Standalone class:尽可能保持低耦合,把其它所有的无关概念提取到对象之外,类变得独立。
Closure of operation(闭合操作):定义操作时,让它的返回类型和其参数类型相同。
声明式设计:通过声明式的设计,如反射,代码自动生成等技术手段来实现功能。
应用分析模式
之前的经验可以借鉴,类似于通用权限模型RBAC。
分析模式是一种概念集合,用来表示业务建模中的常见结构。它可能只与一个领域有关,也可能跨越多个领域。
分析模式是很有价值的知识
这块想起之前看过的《彩色uml建模》那本书,里面介绍了企业软件里常见的业务及相应的设计,如果贴合度高的话,可以拿过来套用。
将设计模式应用于模型
有些设计模式是可以用于领域模型的,有些却不能,如享元模式。
策略模式
我们需要把过程中的易变部分提取到模型的一个单独的策略对象中。将规则 与它所控制的行为分开。按照策略设计模式来实现规则或可替换的过程。策略对象的多个版本完成过程的不同方式。
组合模式
将对象组织为树来表示部分整体的层次结构。利用组合,客户可以对单独的对象和对象组合进行同样的处理。
定义一个反组合的所有成员都包含在内的抽象 类型。在容器上实现那些查询信息的方法时,这些方法返回由容器内容所汇总的信息。而叶节点则基于它们自己的值来实现这些方法,客户只需要全用抽象 类型,而无需区分 叶和容器。
通过重构得到更深层的理解
得到更深层理解的过程需要注意下面三点:
以领域为本
用一种不同的方式来看待事物
始终坚持与领域专家对话
确定是否要开始重构?
解决代码中的问题
模型语言和专家没有保持一致,即使是代码看上去很整洁
通过学习获得了更深刻的理解
探索团队
如何保证重构领域的效率?
自由决定:随时组建临时团队来研究某个问题。
注意范围和休息:时间不能过长,范围不能过大。
使用统一语言
借鉴先前的经验
从书和领域自身的其它知识来源获得思路
从分析模式汲取他人经验
关注设计模式
常见的形式体系(算术逻辑或谓词逻辑)
针对开发人员设计,领域模型设计也要考虑开发人员,更方便开发人员开发。
当发生以下情况时,就应该重构了。
设计没有表达出团队对领域的最新理解
重要的概念被隐藏在设计中了
发现一个能令某个重要的设计部分变得更灵活的机会。
战略设计
之前介绍的都是战术设计,这一章主要是战略上的设计,是更上一层次的设计了。
随着系统的增长,它会变得越来越复杂,当我们无法通过分析对象来理解系统的时候,就需要掌握一些操纵和理解大模型的技术了。
战略设计原则必须把模型的重点放在捕获系统的概念核心,也就是系统的远景上。而且在完成这些目标的同时又不能为项目带来麻烦。为了帮助实现这些目标,这一部分探索了3个大的主题:
上下文:划分系统边界,限界上下文。不同系统的领域模型不一样。
精炼:减少混乱,把注意力集中到正确的地方。
大型结构:用于描述整个系统的。
保持模型的完整性
大型系统领域模型的完全统一是不可行的也不划算。
放在一个模型下面一定要考虑下面的风险:
一次尝试对遗留系统做过多的替换
大项目可能会陷入困境,因为协调的开销太大,超出了这些项目的能力范围
具有特殊需求的应用程序可能不得不使用无法充分满足需求的模型,而只能将这些无法满足的行为放到其它地方
另一方面,试图用一个模型来满足所有人的需求可能会导致模型中包含过于复杂的选择,因而很难使用。
为了解决这些问题,则需要如下概念:
限界上下文
持续集成
上下文地图
选择你的模型上下文策略的指导原则:
团队决策或更高层决策
置身上下文中
转换边界:选择较大边界及较小边界所带来的优缺点。
接受那些我们无法更改的事物:描述外部系统。
与外部系统的关系
设计中的系统
用不同模型满足特殊需要
部署
权衡
精炼
精炼是把一堆混杂在一起的组件分开的过程,以便通过某种形式从中最重要的内容,而这些形式将使它更有价值,也更有用。模型就是知识的精炼。
领域模型的战略精练包括以下部分:
帮助所有团队成员掌握系统的总体设计以及各部分如何协调工作
找到一个具有适度规模的核心模型并把它添加到通用语言中,从而促进沟通
指导重构
专注于模型中最有价值的那部分
指导外包,现成组件的使用以及任务委派
会涉及到下面几个概念:
Core Domain:划分优先级,优先关注核心模型,次要细节先忽略
Generic Subdomain:把子模型提出出来,放在单独的module.
Domain Vision Statement: 有一份核心模型的简短描述,即价值主张。尽早写下来,随时修改它。
Highlighted core:识别核心领域模型及对象间的关系。
Cohesive Mechanism(内聚机制):领域中核心元素专注于表达问题,解决方案的复杂细节转移给了框架。
Segregated Core:对模型进行重构,把核心概念从支持性元素中分离出来,增加core的内聚,同时减少它与其它代码的耦合。
abstract core:把模型中最基本的概念识别出来,并分离到不同的类,抽象类和接口中。通过抽象的模型来表达重要组件及业务。
大型结构
从更高层次来讲解架构
大型结构是一种语言,人们可以用它来从大局上讨论和理解系统。
设计一种应用于整个系统的规则模式,使人们可以通过它在一定程度上了解各个部分在整体中所处的位置。
一个没有任何规则的随意设计会产生一些无法理解整体含义且很难维护的系统。但早期的设计假设又会使项目束手束脚,而且会极大地限制应用程序中某些特定部分的开发人员,设计人员的能力。很快,开发人员就会为了适应结构而不得不在应用程序的开发上委曲求全,要么就是完全推翻架构而又回到没有协调的开发老路上。
让这种概念上的大型结构随着应用程序一起演变,甚至可以变成一种完全不同的结构风格。不要依此过分限制详细的设计和模型决策。
当发现一种大型结构可以明显使系统变得清晰,而又没有对模型开发施加一些不自然的约束时,就应该采用这种结构。使用不合适的结构还不如不使用它。因此最好不要为了追求设计的完整性而勉强去使用一种结构。
当系统一个具体类比正好符合团队成员对系统的想象,并且能够引导它们向着一个有用的方向进行思考时,就应该把这个类比用作一种大型结构,用它来组织设计,并把它吸收到统一语言中。系统隐喻应该既能促进系统的交流,又能指导系统的开发。它可以增加系统不同部分之间的一致性,甚至可以跨越不同的限界上下文,但所有的隐喻都不是完全精确的,因此就不断检查隐喻是否过度或不恰当,当发现它起妨碍作用时随时准备放弃它。
职责分层:如果每个对象的职责都是人为分配的,将没有统一的指导原则和一致性,也无法把领域作为一个整体来处理。为了保持大模型的一致,有必要在职责分配上实施一定的结构化控制。
需要考虑选择适当的层。
对层进行删除,合并,拆分和重新定义等操作时,应寻找并保留以下一些有用的特征。
场景描述
概念依赖性
当我们需要让用户对模型的一部分有所控制,而模型又必须满足更大一组规则时,可以利用知识级别来处理这种情况。它可以使软件具有可配置的行为,其中实体中的角色和关系必须在安装时进行修改。
通过重构得到更适当的结构:
最小化
沟通和自律
通过重构得到柔性设计
通过精炼可以减轻负担
领域驱动设计的综合运用
战略设计的3个基本原则(上下文,精炼和大型结构)并不是可以互相代替的,而是互为补充,并且以多种方式进行互动。
制定战略设计决策的6个要点:
决策必须传达到整个团队
决策过程必须收集反馈意见
计划必须允许演变
架构团队不必把所有最好,最聪明的人员都吸收进来
战略设计需要遵守简约和谦逊的原则
对象的职责要专一,而开发人员应该是多面手
以上是关于designproject学习 DDD 读书笔记的主要内容,如果未能解决你的问题,请参考以下文章