这是InfoQ推出的领域驱动设计精简版。篇幅不长,半天时间能够阅读完。如果大家想了解一下领域驱动的话,可以把这本书当做入门书。书中将领域驱动的核心思想和一些理念进行了介绍。但是本书只是讲述了领域驱动的骨架,缺乏很多血肉,另外里面也没有具体讲述如何在项目中使用领域驱动。如果大家想深入理解的话,建议阅读以下Eric Evans的《领域驱动设计——软件核心复杂性应对之道》,然后再找一个实例做练习。
读完精简版,我的理解是领域驱动是从更高的一个维度来进行软件设计,因为维度高,所以能够更好的把控软件的发展与变化。感觉这些也离不开软件设计的基本理念-高内聚低耦合。另外里面提出了一些问题的解决方案,也是很有借鉴价值的。建议一读。
下面是本书的一些摘要:
##何为“领域驱动设计”
1.“你需要从专家那里尽可能多地学习领域知识。通过提出正确的问题,正确地处理得到的信息,你和专家开始勾勒出领域的视图,也就是领域模型”
通用语言
- “需要确保团队使用的语言在所有的交流形式中看上去都是一致的,而且每个人都是能看懂的”
对公共语言的需要
- 领域驱动设计的一个核心的原则是使用一种基于模型的语言。因为模型是软件满足领域的共同点,它很适合作为这种通用语言的构造基础。
创建通用语言
聊天-画图
模型驱动设计
- “我们应该如何完成从模型到代码的转换?”-“一种更好的方法是将领域建模和设计紧密关联起来。模型在构建时就考虑到软件实现和设计。”
模型驱动设计的基本构成要素
分层架构
- 最好能让应用中的领域部分与其余部分相比保持尽可能小(而不是和其余部分掺杂在一起),因为一个典型的应用包含了大量访问数据库、访问文件或网络、用户界面等相关的代码
- 领域驱动设计的一个通用的架构解决方案包含了4个概念层:
实体
- 定义:“有一类对象看上去好像拥有标识符,它的标识符在历经软件的各种状态变更后仍能保持一致。对这些对象而言,重要的不是其属性,而是其延续性和标识,对象的延续性和标识会跨越甚至能够超出软件系统的生命周期。我们把这样的对象称为实体。” - 有唯一标识的对象可以称为实体
值对象
- 定义:“有的时候我们需要包含一个领域对象的某些属性。我们对它是哪一个对象并不感兴趣,而是只关心它所拥有的属性。用来描述领域的特定方面、并且没有标识符的一个对象,叫做值对象。”
服务
服务的3个特征:
- 服务执行的操作代表了一个领域概念,这个领域概念无法自然地隶属于一个实体或者值对象。
- 被执行的操作涉及到领域中的其他的对象。”
- 操作是无状态的。
模块
- “模块被用来作为组织相关概念和任务以便降低复杂性的一种方法。”
- “如果你查看模快包含的内容以及那些模块间的关系,就会很容易从中掌握大型模型的概况。理解了模块之间的交互之后,人们就可以开始处理模块中的细节了。这是管理复杂性的简单有效的方法。”
- “模块应该具有定义好的接口,这些接口可以被其他的模块访问。最好用访问一个接口的方式,而不是调用模块中的三个对象,因为这样做可以降低耦合度。”
聚合
- “聚合是一个用来定义对象所有权和边界的领域模式。工厂和资源库是另外的两个设计模式,用来帮助我们处理对象的创建和存储问题。”
- “因此,使用聚合。聚合是针对数据变化可以考虑成一个单元的一组关联的对象。聚合使用边界将内部和外部的对象划分开来。每个聚合都有一个根。这个根是一个实体,并且它是外部可以访问的唯一的对象。根对象可以持有对任意聚合对象的引用,其他的对象可以互相持有彼此的引用,但一个外部对象只能持有对根对象的引用。如果边界内还有其他的实体,那些实体的标识符是本地化的,只在聚合内有意义。”
- “根实体拥有全局的标识符,并且有责任维护不变量。内部的实体拥有内部的标识符。”
工厂
“可以帮助封装复杂的对象创建过程,它就是工厂(Factory)”
“为复杂对象和聚合创建实例的职责,应该转交给一个单独的对象。”
“解决方案是给聚合的根添加一个方法,由这个方法来负责对象的创建,强化所有的不变量,返回对那个对象的一个引用或者一个副本。”
“如下情况下应该使用构造器:
·构造过程并不复杂。
·一个对象的创建不涉及到其他对象的创建,可以将所有需要的属性传递给构造器。
· 客户对实现很感兴趣,可能希望选择使用策略(Strategy)模式。
·类是特定的类型,不存在到层级,所以不用在一系列的具体实现中进行选择。”
资源库
- “要使用一个对象,则意味着这个对象已经被创建完毕了。”
- “因此,使用一个资源库,它的目的是封装所有获取对象引用所需的逻辑。”
面向深层理解的重构
持续重构
- “模型必须与它所源自于的领域紧密关联。”
- “重构是不改变应”“用的行为而重新设计代码使得它更好的过程。”
- “除非使用迭代的重构过程,加上领域专家和开发人员一起密切关注对领域的学习,否则一个复杂成熟的领域模式是很难开发出来的。”
凸现关键概念
“第一种发现隐含概念的方式是倾听用到的语言。”
“在将概念显现出来时,还有其他一些非常有用的概念:约束、过程和规约。约束是一个很简单的表达不变量的方式。无论对象的数据如何变化,不变量都要得到保持。”
“处理过程(process)通常在代码中被表达为procedure。”
“规约是用来测试一个对象是否满足特定条件的。规则应该被封装到其自身的一个对象中,这将成为客户的规约,并且被保留在领域层中。”
保持模型的一致性
“模型的首要需求是:模型必须是一致的,保持不变的术语,并且没有矛盾。模型内部的一致性被称为“统一”(unification)。”
“当模型的设计在局部独立进化时,我们就面临着失去模型完整性的可能。努力为整个企业项目维护一个大的统一模型,以获得模型完整性,这个办法也不会有什么作用。”“我们应该做的是有意识地将大模型分解成多个较小的模型。只要遵守它们所绑定的契约,良好整合的小模型能够独立进化。每个模型都应该有一个清晰的边界,模型之间的关系也应该被精确地定义。”
界定的上下文
- “模型的上下文是一些条件的集合,这些条件可以确保应用在模型里的术语都有一个明确的含义。”
持续集成
- “模型不是一开始就被完全定义。而是先被创建,然后基于对领域新的理解和来自开发过程的反馈持续进化。这意味着新的概念会进入模型,新的元素也会被添加到代码中。所有的这些需求都会被集成进一个统一的模型,进而用代码来实现。这也就是为什么持续集成在界定的上下文中如此必要的原因。”“持续集成应用于界定的上下文,不会被用来处理相邻上下文之间的关系。”
上下文映射
- “上下文映射(Context Map)是描绘不同的界定上下文和它们之间关系的一份文档。它可以是像下面所展示的一个图表(diagram),也可以是其他任何形式的文档,细节层次可以有所不同。重要的是,要让每个在项目中工作的人都能够分享并理解它。”
- “共享内核(Shared Kernel)和客户-供应商(Customer-Supplier)是处理上下文之间的高级交互的模式。隔离通道(Separate Way)是在我们想让上下文高度独立和独立进化时要用到的模式。还有两个模式用来处理系统与一个遗留系统或一个外部系统之间的交互,它们是开放主机服务(Open Host Service)和防崩溃层(Anticorruption Layer)。”
共享内核
“共享内核的目的是减少重复,但是仍保持两个独立的上下文。对于共享内核的开发需要多加小心。”
###客户-供应商
- “有的时候两个子系统之间存在特殊的关系:一个子系统严重依赖另一个。两个子系统所在的上下文是不同的,并且一个系统的处理结果被作为另外一个的输入。报表团队应该扮演客户的角色,而在线购物团队应该扮演供应商的角色。两个团队应该定期碰面或者提出碰面邀请,像一个客户对待他的供应商那样交谈。客户团队应该介绍它的需求,而供应商团队根据需求制定计划”
顺从者
- “如果客户不得不使用供应商团队的模型,而且这个模型做得很好,那么就需要顺从这个模型了。客户团队遵从供应商团队的模型,完全顺从它。这和共享内核很相似,但有一个重要的不同之处。客户团队不能对内核做更改。他们只能将它作为自己模型的一部分,可以在所提供的现有代码上完成构建。”
防崩溃层
- “防崩溃层使用外部语言与外部模型交流,而不是客户端语言。这个层在两个领域和语言之间扮演双向转换器,它最大的好处在于可以使客户端模型保持纯洁和一致,不会受到外部模型的污染。”
隔离通道
- “隔离通道模式适合于以下情况:一个企业应用可由几个较小的应用组成,而且从建模的角度来看彼此之间有很少或者没有公共之处。它有一组自己的需求,从用户角度看这是一个应用,但是从建模和设计的观点来看,它可以由具有不同实现的独立模型来完成。我们应该查看一下需求,思考一下它们是否可以被划分成两个或者多个几乎没有相通之处的部分。如果可以这样做,那么我们就创建独立的界定上下文(Bounded Context),并独立建模。这样做的好处是可以自由地选择用来实现模型的技术。我们正创建的应用可能会共享一个公共的瘦GUI,作为带有链接或按钮的一个门户来访问每一个应用。相对于集成后端的模型,将应用组织在一起是一个较小的集成。”
开放主机服务
- “当我们试图集成两个子系统时,通常要在它们之间创建一个转换层。”“当一个子系统要和其他很多子系统集成时,为每一个子系统定制一个转换器会使整个团队陷入困境。”“这个问题的解决方案是,将外部子系统看作服务提供者。如果我们能为这个系统封装一组服务,那么所有的其他子系统将会访问这些服务,我们也就不需要任何转换层。”“定义一个能以一组服务的形式访问你的子系统的协议。将这个协议开放出来,使得所有需要和你做集成的人都能使用它。”
提炼
“其思路是定义一个代表领域本质的核心域(Core Domain)。提炼过程的副产品将是包含了领域中其他部分的普通子域(Generic Subdomain)。”“一旦它们被分离开,就将对它们做持续开发的优先级调得比核心域更低,而且注意不要把你的核心开发人员分配到这些任务中(因为它们从中获取不了多少领域知识)。”
“有下面几种方法可以实现普通子域:”
- 购买现成的解决方案
- 外包
- 已有模型
- 自己实现
参考资料
https://www.infoq.cn/article/domain-driven-design-quickly-new/
“领域驱动设计——软件核心复杂性应对之道”