不全是书上的笔记,夹带了部分私货,加入了个人的看法,有助于我自己理解。没有看完,太理论了,先放放。

第1章 消化知识

关键词:深入业务原理

第1章内容简括

作者从制作一个 PCB 电路板案例,引出了领域建模相关基本要素:

  1. 模型和现实是能对应上的,这和在 OO 中的思想上一样,对象是现实中真实场景的映射
  2. 建立一种非软件工程师也能看懂的模型语言,例如产品、项目、测试、业务、相关领域专家,这里模型语言是 UML 类图
  3. 领域模型的建立是一场不断迭代、进化的行为,这个模型不断有新的概念加入,不需要的概念移出
  4. 领域模型包含丰富的知识,它具有行为和属性
  5. 在领域模型的建立过程中,与领域专家对模型的讨论,这个知识沉淀的过程叫做头脑风暴

第 1 章是消化知识,这个标题看完整个章节才明白这样取名的用意,它要求软件工程师能将某个领域的知识进行归纳、整理,再以一种各方人员都能看的懂的形式输出,完成领域建模。这个过程非常的重要,也极具考察软件工程师抽象能力、沟通能力、对领域的洞察能力、以及快速学习该领域知识的能力。模型是 DDD 的基石,万丈高楼平地起。

目前在我司,开发的需求是来自产品的,开发没有和业务方直接交流的机会,这种情况极具依赖产品的能力。这种情况 DDD 中是强烈反对的,它会失真,知识只能从一个方向流动,没有业务方也就是来自领域专家的反馈,这样的模型建立想想就知道是失败的。DDD 逼迫每一个开发尽可能的深入一线,了解业务,而不是二手的业务知识。(这也是很多开发面临的一个问题,只知道写代码,不理解业务,名副其实的码农)

迭代开发可能存在的缺点

很谨慎的加上了可能。在迭代开发中,如果没有对知识进行抽象从而建立体系的话,这样的软件虽然能用,但是扩展性是极差的,下一次迭代稍有改动,就会面临大改,或者冗余代码,或者堆代码。所以需要开发者去了解业务背后的原理,加深对领域的理解。

DDD 中对知识保护和共享的处理

原文用的是航班货物的案例,这里我用邮件的案例代替了。

这里的知识,是指领域中某个知识点,例如中国国内信件不能超过 20 克,超过了每 20 克就需要再付起始邮费。传统的开发代码可能会这样写:

  1. pubulic float calcPrice(float weight, City city) {
  2. if (weight > 20) {
  3. float a = weight % 20 > 0 ? 1 : 0;
  4. float n = weight / 20;
  5. return city.getStartPrice() * (a + n);
  6. }
  7. return city.getStartPrice() * 1;
  8. }

这个场景中邮件超过 20 克,另外收费就是某个领域中的知识,在软件开发中,不能默认所有的开发都知道这个知识点,所以要设计一个策略,让参与的开发都知道,在邮件中有这个知识细节,可以通过领域模型设计来保护和共享这个知识:

  1. pubulic float calcPrice(float weight, City city) {
  2. return city.calcPriceWithWeight(weight);
  3. }
  4. public City {
  5. pubulic float calcPriceWithWeight(float weight) {
  6. if (weight > 20) {
  7. float a = weight % 20 > 0 ? 1 : 0;
  8. float n = weight / 20;
  9. return city.getStartPrice() * (a + n);
  10. }
  11. return city.getStartPrice() * 1;
  12. }
  13. }

可能会觉得这不就是提取了一个方法了嘛,我平常开发中也有这样写,那么恭喜你,你已经具备了 DDD 中,对模型行为细节的一些思考了,这样做的好处相信开发者都能体会到,尤其是继承了前人代码的开发者,对这种写法表示好评,而不是之前那种在业务中写各种 if-else 逻辑判断。

第2章 交流与语言的使用

关键词:统一语言

领域模型可以作为项目中的通用语言,不管是业务方、测试还是项目文档的编写,都可以用模型来作为高效沟通的手段,模型和开发代码也是紧密绑定的。它可以减少各方的交流成本,让描述更精准。

随着不断的交流、头脑风暴,相关知识的沉淀也是必不可少的,对应的书面文档要及时补充起来。文档不是越多越好,它应该和代码紧密关联的,所以这里的文档面向的是开发者。另外重要的一点,文档要更新,不然文档会成为团队的负资产。

第3章 绑定模型和实现

关键词:领域模型指导开发

DDD 要求模型不仅能够指导早期的分析工作,还应该成为设计的基础。按照模型来编写代码,能够使代码更好的表达设计含义,使模型和实际的系统相契合。

第4章 分离领域

关键词:分层架构

传统的开发中,业务代码和数据库脚本写在一起,这样可以快速完成开发工作。坏处就是如果业务逻辑发生了改变,就要修改这一整块代码,并且要很小心,以免影响到其它程序元素,给测试也带来了很大的麻烦。

Layered Architecture 分层架构,是软件行业一种解决复杂程序的方式,即将一个复杂的分离层多个单一层来处理,每一层只关注一个点,使其更有内聚性。分层种类很多,但大多使用者 4 种:

  1. 表示层(用户界面层),负责对外显示信息和接受并解释外界指令,这里对外的来源可以是用户或者是另一个计算机系统。
  2. 应用层,定义软件要完成的任务,并且使用领域对象来解决问题,这一层非常重要,也是与其它系统应用层进行交互的必要渠道。
  3. 领域层(模型层),负责表达业务概念,业务状态信息以及业务规则,尽管保存业务状态的技术细节是由基础设施层实现的,但是反应业务情况的状态是由本层控制并且使用的,领域层是业务软件的核心
  4. 基础设施层,为上面各层提供通用技术能力,例如:为应用层传递消息,为领域层提供持久化机制,为表示层绘制屏幕组件(可见这本书已经很古老了,这一操作还是 Java 使用 JSP 的时代)等等。基础设施层还能够通过架构来支持 4 个层次间的交互模式。

image.png

这 4 个层,单纯描述这样有点抽象,可以参考下自己公司的代码哪些可以对应这几层。

给复杂的应用程序划分层次,在每一层内进行设计,使其具有内聚性并且只依赖于它的下层。将所有领域模型相关代码放在一个层中,领域对象将重点放在如何表达领域模型上,让模型的含义足够丰富,结构足够清晰,可以捕捉到业务知识,并且有效的使用这些知识。

将各层串联起来

大多数使用 MVC 模式,将各个层串联起来,其实还有其他模式,但是不管什么模式,只要连接的模式能够维持领域层的独立性,那么都是可行的。层与层之间的连接是单向的,上层可以直接使用下层,如果下层要和上层通信,应该使用回调模式或者观察者模式。

第5章 软件中所表示的模型

模型有 3 种元素模式:Entity、Value Object 和 Service。这 3 种分别代表什么含义,这是我们要知道的,明确了含义,才能开发出特定类型的对象。

Entity 模式

Entity 又称 Reference Object,很多对象不是通过他们的属性定义的,而是通过连续性和标识定义的。(记住这句话,很重要,暂时不理解没关系)。

这里要定义下什么是属性,什么是连续性,什么是标识。举例来说,Person - 人 这个对象,姓名就是他的属性,属性是可能改变的;身份证号码就是他的标识,标识是永久不变的;连续性是一种抽象的概念,我暂时还理解不了

Entity 模式中的对象就属于这种,它具有生命周期,这期间它的形式和内容可以发生根本改变,但必须保持一种内在的连续性,为了跟踪它们,必须定义标识,因为属性是可以改变的,无法有效的跟踪。Entity 可以是任何事物,只要满足两个条件,一个是它在整个生命周期是具有连续性的,二是它需要一个标识,它不关心属性的变化。

因此 Entity 对象最重要的就是设计标识了,这个唯一值如何确定?一般采用数据库自增 ID 或者雪花 ID,当然也可以自定义一套算法,保证唯一就行,这个方案就很多了。

PS: Entity 对象类似于我们数据库表映射的一个对象,因为它有主键,具有唯一标识。

Value Object 模式

Value Object(值对象) 没有概念上的标识,它描述了一个事物的某种特征。当我们只关心属性时,我们就可以把这类对象归类为值对象。

PS:值对象经常作为参数,用来传递信息,是个临时对象,使用完即可丢弃。

Service 模式

有时候对象不是一个事物,而是一个些特殊的操作或行为,它们不适合被建模为对象,因此引入一种新的模式,Service(服务)。

Service 是作为接口提供的一种操作,它在模型中是独立的,它定义了一些行为操作,强调能够做什么,因此它是个动词,并且它的操作是无状态的。但使用它应该谨慎,切记把 Entity 和 Value Object 中的对象行为都放在 Service 中。

第6章 领域对象的生命周期

复杂的对象创建是领域层的职责。如何创建一个复杂对象,可以用 Factory 模式 或者 Builder 模式,Factory 隐藏了创建对象的细节。

Repository 模式

Repository 可以理解为传统的 Dao 层,负责数据库访问,但它和 Dao 的本质区别在于,我感觉没有什么区别。Repository 负责所有对象的存储和访问,如果用过 Spring JPA 那么对 Repository 理解的会更容易。Repository 不会处理事务问题,事务应该交给客户端来实现。Entity 对象才能有 Repository。

第7章 使用的语言:一个扩展的示例

导如何在实际中使用 DDD,后面单独出一个教程。给自己先挖个坑。

第9章 如何为那些不明显的概念建模

业务规则不适合作为 Entity 和 Value Object 的职责,业务规则的变化会掩盖领域对象的基本含义,但是将这些规则移出领域层又会表达不出模型用意。

提取出来,将行为封装。

第10章 柔性设计

柔性设计:设计出来的代码是要让后面的人乐于使用它、乐于修改它,而不是很烂的代码,不敢重构。

一好的组件对外表现是,使用者不需要知道细节,知道怎么用就行了。