读书笔记-实现领域驱动设计

image.png

概览

DDD总览
早些时候,我讲到了DDD的通用语言(Ubiquitous Language, 1)。通用语言作用于某个限界上下文(Bounded Context, 2) ,它对于领域建模是非常重要的,你应该好好地熟悉一下。 请记住,不管你是在战术上还是战略上设计软件模型,你都应该保证:在一一个特定的限界上下文中只使用一套通用语言,并且保证它的清晰性和简洁性。
战略建模
限界上下文是一-种概念上的边界,领域模型便工作于其中。同时,限界上下文为通用语言提供了一°套环境,项目成员便通过通用语言来表达软件模型,如图G.1所示。
image.png
上下文映射
image.png
六边形架构
image.png
聚合
image.png
在领域模型中,有些业务操作并不能自然地放在实体或值对象上,此时我们可以使用无状态的领域服务(Domain Service) ,如图G.5所示。
image.png
领域事件(Domain Event, 8)表示领域模型中发生的重要事件。有多种方式可以对领域事件进行建模。在对聚合进行命令操作时,聚合本身将发布领域事件,如图G.6所示。
image.png
我们通常忽略了模块(Module, 9),但是正确地设计模块同样是重要的。简单来讲,我们可以将模块看成是Java中的包或C#中的命名空间。请记住,如果只是机械式地设计模块,而不是根据通用语言,那么我们将得不偿失。模块中包含的领域对象应该是内聚在一起的,如图G.7所示。

image.png

第一章 DDD入门

什么是领域模型?

领域模型是关于某个特定业务领域的软件模型。通常,领域模型通过对象模型来实现,这些对象同时包含了数据和行为,并且表达了准确的业务含义。

将领域专家引入到团队是大有好处的。

在实施DDD的过程中,你最好将那些不怎么使用技术语言的人加进自己的团队。就像你会向他们学习一样,他们也会向你学习。

为什么需要DDD?

  • 使领域专家和开发者在一起工作,这样开发出来的软件能够准确地传达业务规则。当然,对于领域专家和开发者来说,这并不表示单单地包容对方,而是将他们组成-个密切协作的团队。
  • “准确传达业务规则”的意思是说,此时的软件就像如果领域专家是编码人员时所开发出来的一样。
  • 可以帮助业务人员自我提高。没有任何一个领域专家或者管理者敢说他对业务已经了如指掌了,业务知识也需要- -个长期的学习过程。在DDD中,每个人都在学习,同时每个人又是知识的贡献者。
  • 关键在于对知识的集中,因为这样可以确保软件知识并不只是掌握在少数人手中。
  • 在领域专家、开发者和软件本身之间不存在“翻译”,意思是当大家都使用相同的语言进行交流时,每人都能听懂他人所说。
  • 设计就是代码,代码就是设计。设计是关于软件如何工作的,最好的编码设计来自于多次试验,这得益于敏捷的发现过程。
  • DDD同时提供了战略设计和战术设计两种方式。战略设计帮助我们理解哪些投人是最重要的;哪些既有软件资产是可以重新拿来使用的;哪些人应.该被加到团队中?战术设计则帮助我们创建DDD模型中各个部件。

正如[Fowler, Anemic]所说,贫血领域对象是不好的,因为你花了很大的成本来开发领域对象,但是从中却获益甚少。比如,由于存在对象-关系阻抗失配(Object- Relational Impedance) ,开发者需要将很多时间花在对象和数据存储之间的映射上。这样的代价太大,而收益太小。我得说,你所说的领域对象根本就不是领域对象,而只是将关系型数据库中的模型映射到了对象.上而已。这样的领域对象更像是活动记录(Active Record) [Fowler, P of EAA],此时你可以对架构做个简化,然后使用事务脚本(Transaction Script) [Fowler, P of EAA]进行开发。

通用语言

通用语言是团队共享的语言。领域专家和开发者使用相同的通用语言进行交流。事实上,团队中每个人都使用相同的通用语言。不管你在团队中的角色如何,只要你是团队的一员,你都将使用通用语言。

  • 同时绘制物理模型图和概念模型图,并标以名字和行为。虽然这些图并不是正式的设计图,但它们却包含了软件建模的某些方面。即使你的团队在使用统一建模语言(Unified Modeling Language, UML )来完成正式建模,也不要得意忘形,因为这样可能反而不利于团队的讨论,最终将阻碍通用语言的产生。

  • 创建一个包含简单定义的术语表。将你能想到的术语都罗列出来,包括好的和不好的,并注明好与不好的原因。在你给术语下定义时,你在不经意间就会创造出一些可 重用的词汇,因为此时你使用的是领域中的通用语言。

  • 如果你不喜欢术语表,可以采用其他类型的文档,但记得将那些“不正式”的模型图也包含进去。同样,这里最终的目的也是发现通用语言中的术语和词组。

  • 由于团队中有些人工作在术语表上,还有些人工作在文档上,此时你需要找到团队的其他人员来检查你的成果。分歧肯定是有的,你应该对此有所准备。

是通用,不是万能

  • 这里的“通用”意思是“普遍的”,或者“到处都存在的”。通用语言在团队范围内使用,并且只表达一个单一的领域模型。
  • “通用语言”并不表示全企业、全公司或者全球性的万能的领域语言。
  • 限界上下文和通用语言间存在一对一的关系。
  • 限界上下文是一个相对较小的概念,通常比我们起初想象的要小。限界上下文刚好能够容纳下一个独立的业务领域所使用的通用语言。
  • 只有当团队工作在一个独立的限界上下文中时,通用语言才是“通用”的。
  • 虽然我们只工作在-一个限界上下文中,但是通常我们还需要和其他限界上下文打交道,这时可以通过上下文映射图(3)对这些限界上下文进行集成。
  • 每个限界上下文都有自己的通用语言,而有时语言间的术语可能有重叠的地方。
  • 如果你试图将某个通用语言运用在整个企业范围之内,或者更大的、夸企业的范围内,你将失败。

DDD业务价值

  • 你获得了一个非常有用的领域模型
  • 你的业务得到了更准确的定义和理解
  • 领域专家可以为软件设计做出贡献
  • 更好的用户体验
  • 清晰的模型边界
  • 更好的企业架构
  • 敏捷、迭代式和持续建模
  • 使用战略和战术新工具

战术建模工具

  • 聚合(Aggregate, 10)、
  • 实体(Entity, 5)、
  • 值对象(Value Object, 6)、
  • 领域服务(Domain Service, 7)
  • 领域事件(Domain Event, 8)等。

实施DDD所面临的挑战

  • 为创建通用语言腾出时间和精力
  • 持续地将领域专家引入项目
  • 改变开发者对领域的思考方式

第一个例子,通常的做法,使用属性访问的方式:
image.png
客户端代码
image.png
第二个例子使用了领域对象的行为,这种行为表达出了领域中的通用语言:
image.png
客户端代码
image.png
第一个例子采用的是以数据为中心的方式,此时客户代码必须知道如何正确地将-个待定项提交到冲刺中。这样的模型是不能称为领域模型的。如果客户代码错误地修改了sprintld,而没有修改status会发生什么呢?或者,如果在将来有另外一个属性需要设值时又该怎么办?我们需婴认真分析客户代码来完成从客户数据到Backlogltem属性的映射。

这种方式同时也暴露了BacklogItem的数据结构,并且将关注点集中在数据属性上,而不是对象行为。你可能会反驳道:“setSprintld()和setStatus( )就是行为啊。”问题在于,这里的“行为”没有真正的业务价值,它并没有表明领城模型中的概念。此处即“将待定项提交到冲刺中”。开发者在开发客户代码时,他并不清楚到底需要为Backlogltem的哪些属性设值,而这样的属性有可能存在很多,因为这是一个以数据为中心的模型。

现在,我们来看看第二个例子。有别于第-个例子,它将行为暴露给客户,行为方法的名字清楚地表明了业务含义。这个领域的专家在建模时讨论了以下需求:

允许将每一个待定项提交到冲刺中。只有在一个待定项位于发布计划(Release) 中时才能进行提交。如果-个待定项已经提交到了另外一个冲刺中。那么需要先将其回收。提交完成时,通知相关客户方。

在第二个例子中,客户代码并不需要知道提交Backlogtem的实现细节。实现代码所表达的逻辑恰好能够描述业务行为。我们很容易地添加了几行代码,以确保在发布计划之外的待定项是不能被提交的。诚然,在第-个例子中,你可以修改setter以达到同样的目的,但此时该eter的职责便不单-了它需要了解Backlogltem对象的内部状态,而不再只是对sprintld和Istatus属性赋值。

为领域建模正名

  • 如果一个限界上下文被当成核心域来开发,那么从战略上来说,这个限界上下文对业务的成功是极其重要的。核心模型是不易理解的,需要不断地尝试和重构。通过持续改进,我们可以延长它的效用生命,这样的做法显然是值得的。当然,这个限界上下文不见得始终是你的核心域。即便如此,如果它是复杂的,创新性的,并且需要在不断的变化中持续存在很长时间,我们还是建议在该限界上下文中使用战术模式。这里,我们假设你的核心域是值得配置最好的开发者的。
  • 一个领域,对于消费方来说有可能成为通用子域(Generic Subdomain, 2)或者支撑子域,但是却有可能成为你自己的核心域。我们并不站在最终消费方的角度来评价一个领域。如果你正在开发的限界上下文是你主要的业务,那么它便是你的核心域,而不管消费方是如何看待的。此时,一定记得使用战术模式。
  • 如果你正开发一个支撑子域,但是由于种种原因,该支撑子域不能从第三方的通用子域直接获得,那么此时战术模式将帮上你大忙。在这种情况下,你需要考虑团队成员的技能水平,还有模型是否具有创新性。如果此时的模型增加了特定的业务价值,而且不只是拥有技术上的绚丽,那么该模型就可以认为是创新性的。如果团队有能力实施战术设计,这个支撑子域又是创新性的,并且将持续存在很长时间,那么此时便是采用战术设计的大好时机。尽管如此,这并不能使该子域称为核心域,因为在业务人士眼中,这样的领域只是支撑性的。

DDD并不笨重

在我看来,DDD绝非是充满繁文缛节的笨重开发过程。事实上, DDD能够很好地与敏捷项目框架结合起来,比如Scrum。DDD也倾向于“测试先行,逐步改进”的设计思路。在你开发一个新的领域对象时,比如实体或值对象,你可以采用以下步骤进行:

  • 1.编写测试代码以模拟客户代码是如何使用该领域对象的。
  • 2.创建该领域对象以使测试代码能够编译通过。
  • 3.同时对测试和领域对象进行重构,直到测试代码能够正确地模拟客户代码,同时领域对 象拥有能够表明业务行为的方法签名。
  • 4.实现领域对象的行为,直到测试通过为止,再对实现代码进行重构。
  • 5.向你的团队成员展示代码,包括领域专家,以保证领域对象能够正确地反映通用语言。

第二章 领域、子域和限界上下文

总览

从广义上讲,领域(Domain)即是一个组织所做的事情以及其中所包含的一切。商业机构通常会确定一个市场,然后在这个市场中销售产品和服务。每个组织都有它自己的业务范围和做事方式。这个业务范围以及在其中所进行的活动便是领域。当你为某个组织开发软件时,你面对的便是这个组织的领域。这个领域对于你来说应该是明晰的,因为你在这个领域中工作。

p77

第三章 上下文映射图

第四章 架构

第五章 实体

第六章 值对象

第七章 领域服务

第八章 领域事件

第九章 模块

第十章 聚合

第十一章 工厂

第十二章 资源库

第十三章 集成限界上下文

第十四章 应用程序

资料

链接: https://pan.baidu.com/s/1yCZcsPFHyBlkRDVEKP05Fw 密码: s8fo


读书笔记-实现领域驱动设计
https://mikeygithub.github.io/2021/10/06/yuque/读书笔记-实现领域驱动设计/
作者
Mikey
发布于
2021年10月6日
许可协议