《领域驱动设计精粹》沃恩.弗农_2018

第1章 开篇

优秀设计、糟糕设计和有效设计

  • 有很多软件开发团队的设计从来经不起思考。他们采用一种我称之为“任务板挪卡”的方法来代替设计。这种情况常常是因为团队必须按照苛刻得近乎残忍的时间表去发布软件,管理层只会使用Scrum控制交付节奏,却对它最重要的信条之一视而不见:“知识获取”
  • 臆想出来的“不做设计能省钱”的观念简直是一个谬论,它已经巧妙地愚弄了那些不思考周详设计而只会对软件交付施压的人们
  • 设计是不可或缺的。除了优秀设计就是糟糕设计,根本不存在“不做设计”一说
  • 有效设计”(EffectiveDesign)。有效设计可以满足商业组织希望借助软件超越竞争者的诉求。它可以驱动企业去思考哪些核心业务必须成为其竞争力,还可以指引构建正确软件模型的方向

战略设计

  • 在展开具体实现细节之前,需要优先完成宏观层面的战略设计。它强调的是业务战略上的重点,如何按重要性分配工作,以及如何进行最佳整合
  • 限界上下文(BoundedContext):战略设计模式,来分离领域模型
  • 通用语言:在明确的限界上下文中发展一套领域模型的通用语言
  • 子域(Subdomain):通过它处理遗留系统中无边界的复杂性,以及改进新项目上的成果
  • 上下文映射(Context Maping):集成多个限界上下文
  • 上下文映射图(Context map):同时定义了两个进行集成的限界上下文之间的团队间关系及技术实现方式

战术设计

  • 聚合(Aggregate)模式:用来将若干实体和值对象以恰当的大小聚集在一起
  • 领域事件(Domain Events):既可以让你明确地建立模型,也可把模型内部发生的事情分享给需要知道这一切的系统。这些相关的系统可能是你自己的本地限界上下文和其他的远程限界上下文

第2章 运用限界上下文与通用语言进行战略设计

限界上下文

  • 限界上下文是语义和语境上的边界。这意味着边界内的每个代表软件模型的组件都有着特定的含义并处理特定的事务。限界上下文中的这些组件有特定的上下文语境和语义理据
  • 你可以将它理解为问题空间(ProblemSpace)的一部分。然而,随着软件模型开始呈现出更深层次以及更清晰的含义时,限界上下文将会被迅速转换到解决方案空间(SolutionSpace)中,同时软件模型将通过项目的源代码来体现
  • 当限界上下文被当作组织的关键战略举措进行开发时,即被称之为核心域
  • 大泥球(Big BallofMud):这个系统由多个没有明确边界并纠缠在一起的模型组成。更为严重的是,它可能还会要求多个团队在其中工作。此外,各种毫不相干的概念充斥在众多的模块中,并与自相矛盾的元素相互关联

领域专家和业务驱动

  • DDD强调将这些不同的概念类型分离到不同的限界上下文中,以此来拥抱这些差异,并且承认存在不同的通用语言和与之对应的职能

战略设计是必要的根基

  • 采用限界上下文会迫使我们回答“什么是核心?”的问题。它应紧紧地抓住战略举措中所有的核心概念,并排除其他概念,剩下的都应该是团队通用语言的一部分
  • 只有经过“仅限核心”的严格过滤之后保留下来的概念,才能成为拥有限界上下文的团队的通用语言的一部分。限界上下文的边界强调其内部的严谨性
  • 我们该如何确定核心?为此,我们必须将两个重要的群体——领域专家(Domain Expert)和软件开发人员,整合成一个有凝聚力的协作团队
  • 专注业务复杂性而非技术复杂性

发展通用语言

– 什么啊…

架构

在这里插入图片描述

  • 虽然在整个架构体系中遍布着各种技术,但领域模型本身与技术无关。这就是为什么事务是由应用服务管理,而不是由领域模型来管理的原因
  • 你可以在DDD中使用任何架构,或架构模型
    • 端口和适配器
    • 事件驱动架构,事件溯源
    • 命令和查询职责分离(CQRS)
    • 响应式架构和Actor模型
    • 具象状态传输(REST)
    • 面向服务的架构(SOA)
    • 微服务架构
    • 云计算

第3章 运用子域进行战略设计

DDD项目中总会碰到很多限界上下文(Bounded Contexts)。这些上下文中一定有一个将成为核心域(CoreDomain),而其他的限界上下文之中也会存在许多不同的子域(Sub Domain)
在这里插入图片描述

什么是子域

  • 子域代表的是一个单一的、有逻辑的领域模型
  • 子域可以用来逻辑地拆分整个业务领域,这样才能理解存在于大型复杂项目中的问题空间
  • 子域是一个明确的专业领域,假设它负责为核心业务提供解决方案

子域类型

  • 核心域(Sub Domain):它是一个唯一的、定义明确的领域模型,要对它进行战略投资,并在一个明确的限界上下文中投入大量资源去精心打磨通用语言。你的组织无法在所有领域都出类拔萃,所以必须把核心域打造成组织的核心竞争力
  • 支撑子域(Supporting Subdomain):这类建模场景提倡的是“定制开发”,因为找不到现成的解决方案。对它的投入无论如何也达不到与核心域相同的程度。也许会考虑使用外包的方式实现此类限界上下文,以避免因错误地认为其具有战略意义而进行巨额的投资
  • 通用子域(Generic Subdomain):通用子域的解决方案可以采购现成的,也可以采用外包的方式,抑或是由内部团队实现,但我们不用为其分配与核心域同样优质的研发资源,甚至都不如支撑子域

应对复杂性

  • 有些遗留系统与强调限界上下文的DDD设计方式大相径庭,甚至可以称之为无边界(unbounded)遗留系统。这样的遗留系统正是我提到的“大泥球”。事实上,“大泥球”内充满了多个错综复杂的模型,这些模型本应被分别设计并实现,但当它们纠缠在一起时,整个系统会乱成一团
    在这里插入图片描述

  • 当我们在讨论某个遗留系统时,其中可能会包含一些甚至许多逻辑领域模型。我们将每个逻辑域模型当成一个子域对待

  • 上图中,无边界遗留单体大泥球中,每个逻辑子域都已经被虚线框标记出来。共有五个逻辑模型或子域。这样处理逻辑子域的方式有助于我们应对大型系统的复杂性。这很有意义,因为我们可以像使用DDD和多个限界上下文应对问题空间一样,为其提供解决方案

  • 使用子域来思考和讨论此类遗留系统有助于我们应对大型错综复杂模型的残酷现实。当使用这类工具时,我们可以明确那些对业务更有价值、对项目更重要的子域,而其他子域可以降低到次要位置

  • 当无法将支撑子域与核心域拆成两个限界上下文时,应该如何使用DDD模块分离它们
    在这里插入图片描述

第4章 运用上下文映射进行战略设计

  • 除了核心域(Sub Domain)之外,每个DDD项目还关联着多个限界上下文。所有不属于敏捷项目管理上下文(即核心域)的概念都会被迁移到其他某个限界上下文之中
  • 敏捷项目管理核心域必须和其他限界上下文进行集成。这种集成关系在DDD中称为上下文映射(Context Mapping)
  • 你可以在上面的上下文映射图(Context Map)中看到,Discussion同时存在于两个限界上下文(Bounded Context)之中。这是因为协作上下文是Discussion的来源,而敏捷项目管理上下文是Discussion的消费者
    在这里插入图片描述

映射的种类

合作关系(Partnership)

合作关系关系存在于两个团队之间。每个团队各自负责一个限界上下文
在这里插入图片描述

共享内核(SharedKernel)

两个(或更多)团队之间共享着一个小规模但却通用的模型
在这里插入图片描述

客户—供应商(Customer-Supplier)

供应商位于上游(图中的U),客户位于下游(图中的D)。支配这种关系的是供应商,因为它必须提供客户需要的东西。客户需要与供应商共同制订规划来满足各种预期,但最终却是由供应商来决定客户获得的是什么以及何时获得
在这里插入图片描述

跟随者(Conformist)

上游团队没有任何动机满足下游团队的具体需求。由于各种原因,下游团队也无法投入资源去翻译上游模型的通用语言来适应自己的特定需求,因此只能顺应上游的模型。当一个团队需要与一个非常庞大复杂的模型集成,而且这个模型已经非常成熟时,团队往往会成为它的跟随者
在这里插入图片描述

防腐层(AnticorruptionLayer)

防腐层(AnticorruptionLayer)是最具防御性的上下文映射关系,下游团队在其通用语言(模型)和位于它上游的通用语言(模型)之间创建了一个翻译层。防腐层隔离了下游模型与上游模型,并完成两者之间的翻译
但凡有可能,你就应该尝试在下游模型和上游集成模型之间创建一个防腐层,这样才可以在你这端的集成中创造出特别适合业务需求的模型概念,并将外部概念完全地隔离。然而,就像为两个讲不同语言的团队雇佣翻译来解决沟通问题一样,在某些情况下各方面的成本会水涨船高
在这里插入图片描述

开放主机服务

开放式主机服务(OpenHostService)会定义一套协议或接口,让限界上下文可以被当作一组服务访问。该协议是“开放的”,所有需要与限界上下文进行集成的客户端都可以相对轻松地使用它。通过应用程序编程接口(API)提供的服务都有详细的文档,用起来也很舒服。即使是处在类似图中团队2的位置,也没有时间在这端的集成中创建隔离用的防腐层,和许多可能遇到的遗留系统相比,作为团队1模型的跟随者更容易被接受。可以说,开放主机服务的语言比其他类型的系统语言更易用
在这里插入图片描述

已发布语言(PublishedLanguage)

是一种有着丰富文档的信息交换语言,可以被许多消费方的限界上下文简单地使用和翻译。需要读写信息的消费者们可以把共享语言翻译成自己的语言,反之亦然,而在此过程中它们对集成的正确性充满信心
同时提供和使用已发布语言的开放主机服务可以为第三方提供最佳的集成体验。这种结合使得两种通用语言之间的转译非常方便
在这里插入图片描述

各行其道(SeparateWay)

使用各种通用语言来与一个或多个限界上下文集成这样的方式不能产生显著的回报。也许你所寻求的功能并不能由任何一种通用语言提供。在这种情况下,只能在限界上下文中创造属于自己的特殊解决方案,并放弃针对这种特殊情况的集成

大泥球(BigBallofMud)

如何一步一步把系统推向大泥球深渊:

  • 越来越多的聚合因为不合理的关联和依赖而交叉污染
  • 对大泥球的一部分进行维护就会牵一发而动全身,解决问题就像在“打地鼠”
  • 只剩下“部落知识”和“个人英雄主义”,唯有同时“讲”出所有语言的极个别“超人”方能扶大厦之将倾
    如果必须与一个或多个这样的大泥球系统集成,请尝试针对每个这样的遗留系统创建一个防腐层,保护自己的模型免受污染,否则会陷入难以理解的泥潭
    在这里插入图片描述

善用上下文映射

可以是基于SOAP的RPC,也可以是基于资源的RESTful接口,抑或是使用队列或发布订阅的消息机制。最不济你会被迫使用数据库或文件系统进行集成,让我们祈祷这种情况不会发生。基于数据库的集成方式是一定要避免的,如果不得不以这种方式进行集成,请务必通过防腐层来隔离要去集成和适配的模型
在这里插入图片描述

RPC

  • 在一开始实施这种集成方式时就要承受网络彻底瘫痪的风险,或者至少要承受意外的网络延迟。RPC还意味着客户端限界上下文和提供服务的限界上下文之间存在着紧耦合
  • RPC的主要问题是缺乏健壮性。如果网络出现问题或者托管API的系统出现问题,那么看似简单的过程调用将完全失败,只会留下错误的结果。不要被看似易用的表面所迷惑
  • 不管怎样,客户端限界上下文都可以设计一层防腐层,将模型与多余的外部影响隔离开来
    在这里插入图片描述

RESTful HTTP

造成RESTful HTTP失败的原因通常和许多造成RPC失败的原因一样——网络或服务提供商故障,还有意外延迟。在没有网络的前提下,RESTful HTTP无法运作
使用REST常犯的设计错误是直接把模型中的聚合暴露成资源。服务端模型一旦发生变化,资源也会随之一起改变,这样会把跟随者关系强加给每个客户端。所以你不会想这样做。相反,应该根据客户端驱动的用例设计出“合成”的资源。所谓“合成”,是指对客户端来说,服务端提供出来的资源必须具有它们所需要的样子和组成,而不是直接给出实际的领域模型

消息机制

在使用异步消息机制进行集成时,很多工作都是通过客户端限界上下文订阅由它自己或另一个限界上下文发布的领域事件(DomainEvents)来完成的。使用消息机制是最健壮的集成方法之一,因为可以消除那些和阻塞(同步)形式(如RPC和REST)有关的暂时性耦合。如果可以提前预见到消息交换会产生延迟,就可以构建出更健壮的系统,因为你从未期望结果会即时发生
在这里插入图片描述

  • 在使用消息进行集成的所有用例中,整体解决方案的质量很大程度上将取决于所选消息机制的质量。消息机制应支持至少一次投递[Reactive]来保证所有消息最终都会被收到。这也意味着订阅方限界上下文必须实现成幂等接收者(Idempotent Receiver)[Reactive]

第5章 运用聚合进行战术设计

– 这一章翻译得真恶心

  • 一个实体模型就是一个独立的事物。每个实体都拥有一个唯一的标识符,可以将它的个体性和所有其他类型相同或者不同的实体区分开
  • 聚合是什么?这里展示了两个聚合。它们都是由一个或多个实体组成,其中一个实体被称为聚合根(AggregateRoot)。聚合的组成还可能包括值对象。就像这里看到的,两个聚合中都用到了值对象
  • 每个聚合都会形成保证事务一致性的边界。这意味着在一个单独的聚合中,在控制被提交给数据库的事务时,它的所有组成部分必须根据业务规则保持一致。这并非意味着你不应该把那些事务完成之后不一致的元素组合到聚合中
    在这里插入图片描述

聚合的经验法则

  • 在聚合边界内保护业务规则不变性
  • 聚合要设计得小巧
  • 只能通过标识符引用其他聚合
  • 使用最终一致性更新其他聚合

建立聚合模型

第6章 运用领域事件进行战术设计

设计、实现并运用领域事件

消息?

事件溯源

事件溯源(Event Sourcing)可以描述为,对所有发生在聚合实例上的领域事件进行持久化,把它们当作对聚合实例变化的记录。你存储的是发生在聚合上的所有独立事件,而不是把聚合状态作为一个整体进行持久化

第7章 加速和管理工具

事件风暴

  • 以事件为中心的方式建模
  • 令每个人都聚焦于事件和业务流程,而不是类和数据库
  • 宏观(Big-Picture)的建模和设计级别(Design-level)的建模都可以使用事件风暴。宏观的事件风暴不追求细节,而设计级别的事件风暴会引导你完成一些特定的软件产出物
  • 不必强求一次风暴讨论就能解决所有问题。可以先从一次两小时左右的风暴讨论开始。如果每天两小时,连续做上三四天,你将会深入地理解核心域以及它和周边子域之间的集成

1 创建领域事件,梳理业务流程

通过创建一系列写在便利贴上的领域事件,快速梳理出业务流程。最流行的代表领域事件的便利贴颜色是橘色。橘色让建模平面上的领域事件最显眼突出

  • 在创建领域事件时要强调我们优先和主要关注的是业务流程,而不是数据及其结构
  • 把每个领域事件的名称写在一张便利贴上。在前面的章节中已提到,事件的名称应该是动词的过去式。例如,可以命名某个事件为ProductCreated,而命名另一个事件为BacklogItemCommitted
  • 把这些写好事件的便利贴按照时间顺序摆放在建模平面上,即按照每个事件在领域中发生的先后顺序从左到右排列
  • 按照业务流程,有些领域事件会和其他事件并行发生,可以把这些事件摆放在同时发生的领域事件的下方。这样,就可以使用纵向的空间来表示并行处理
  • 在风暴讨论的这个步骤中,会在已有的或新的业务流程中发现问题点。将它们清楚地记录在紫/红色的便利贴上,并用一段文字解释为什么它是一个问题点。你需要在这些问题点上投入更多的时间来学习
  • 有时领域事件将导致一个需要执行的流程(Process)。流程可以是一个单独步骤,也可以是多个复杂步骤。由每个领域事件导致执行的流程都应该被命名并记录在浅紫色的便利贴上。请从领域事件开始绘制一条带箭头的连线,最终指向这个命名流程(浅紫色便利贴)。只用对那些核心域中非常重要的细粒度领域事件进行建模
  • 如果你认为已经穷尽了所有可能的重要领域事件,那么可以休息一下,稍后再回到建模的讨论中。在某个时刻,你将识别出大部分最重要的领域事件。这时你应该继续下一步
    在这里插入图片描述

2 创建导致每个领域事件发生的命令

  • 在浅蓝色便利贴上,写下导致每个领域事件发生的对应命令的名称。例如,如果有一个名为BacklogItemCommitted的领域事件,导致该事件发生的对应命令的名称是CommitBacklogItem
  • 把代表命令的浅蓝色便利贴紧挨着摆放在由它引起的领域事件的左边。它们被成对地关联在一起:命令/事件、命令/事件、命令/事件
  • 如果存在一个执行动作的特定用户角色,并且这一点很重要,可以在浅蓝色命令的左下角贴上一张亮黄色的小便利贴,画上一个简笔小人并写上角色的名称
  • 有时命令将会导致流程的执行。流程可以是一个单独步骤,也可以是多个复杂步骤。每个命令导致执行的流程都应该被命名并记录在浅紫色的便利贴上。从命令开始绘制一条带箭头的连线,最后指向这个命名流程(浅紫色便利贴)
  • 按照从左到右的时间顺序继续处理下一个命令/事件对,和先前创建领域事件时一样
  • 创建命令很有可能让你想到一些之前没有预料到的领域事件(比如上面发现的浅紫色流程或者其他事件)。继续把新发现的领域事件和它对应的命令摆放在建模平面上,记录下这些新的发现
  • 你还会发现一个命令可能导致多个领域事件发生。这很正常。创建一个命令,把它摆放在那些由它引起的所有领域事件的左边
    在这里插入图片描述

3 关联命令和领域事件

把命令和领域事件通过实体/聚合关联起来,命令在实体/聚合上执行并产生领域事件的结果。实体就是命令执行和领域事件触发的数据载体

  • 如果业务人员不喜欢聚合这个词,或者这个词以任何形式干扰了他们,就应该使用其他名称。通常他们可以理解实体,或者干脆称之为数据
  • 把命令和领域事件便利贴一起贴在聚合便利贴上,聚合便利贴稍微靠上和其他两张便利贴错开一些。换句话说,你应该能看到聚合便利贴上的名词,而命令和领域事件便利贴应该分别贴在聚合便利贴的左下角和右下角,这样表明它们是关联在一起的
  • 沿着业务流程的时间线移动,你很可能会发现一个聚合被反复地使用。不用调整时间线让所有命令/事件对都贴在同一张聚合便利贴上,而应该用多张便利贴,都写上同一个聚合的名字,分别贴在时间线上对应命令/事件对出现的地方。我们的重点是对业务流程建模,而业务流程是按时间发生的
  • 当你思考和各种操作相关的数据时,可能会发现新的领域事件。不要忽视这些事件,而应该将新发现的领域事件和对应的命令和聚合记录下来,放在建模平面上。你还可能会发现某些聚合过于复杂,需要将它们拆分成一个托管的流程(浅紫色便利贴),不要放过任何改善的机会
  • 事件风暴越宏观,它离真实的实现就越远。不过,也可以使用同样的技术来完成设计级别的建模。团队倾向于在同一次事件风暴的讨论中,不断切换宏观和设计级别的视角。最后,对某些细节理解和学习的追求会驱使你超越大局观,接近最基本的设计级别的模型
    在这里插入图片描述

4 在建模平面上画出边界和表示事件流动的箭头连线

  • 你非常有可能在下面这些条件满足时发现边界:部门分界出现时、不同业务人员对相同术语的定义出现冲突时,或者非常重要但不属于核心域的某个概念出现时
  • 可以用黑色马克笔在建模平面的白纸上绘制边界。要把上下文边界和其他类型的边界区分开。使用实线表示限界上下文边界,使用虚线表示子域边界
  • 把粉红色便利贴摆在不同的区域边界内,并在这些便利贴上写上代表该区域内容的名字。这就是在给限界上下文命名
  • 绘制箭头连线来表示领域事件在限界上下文之间的流动方向。这是一种交流领域事件如何抵达系统的简单方法,这些领域事件并非由限界上下文中的命令引起
    在这里插入图片描述

5 识别用户执行操作所需的各种视图(View),以及不同用户的关键角色

  • 不一定展示用户界面提供的所有视图,或者根本不需要展示任何视图。你觉得需要展示的任何视图都应该是非常重要的并且在创建时需要特别留意。这些视图产出物可以用建模平面上的绿色便利贴表示。请绘制那些最重要的用户界面视图的快速原型(或者线框图),如果这样做有帮助的话
  • 你还可以使用亮黄色便利贴来表示各种不同的重要用户角色。再次重申,只有当关于用户和系统交互的重要事项,或者系统针对用户的特定角色要完成的事情需要交流时,才需要展示这些内容
    在这里插入图片描述

其他工具

  • 影响力地图[Impact Mapping],确保你设计的软件是核心域,而不是一些不太重要的模型
  • 用户故事地图[User Story Mapping]。可以通过这种方法将重点放在核心域,并搞清楚应该将资源投入哪些软件特性
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值