领域驱动设计理解&总结

领域驱动设计理解&总结 这篇文章主要是通读《实现领域驱动设计》之后自己的理解和总结(同时也参照一些博文的分析来加深自己的理解); 有些疑问是自定义内容,虽然有

领域驱动设计理解&总结 这篇文章主要是通读《实现领域驱动设计》之后自己的理解和总结(同时也参照一些博文的分析来加深自己的理解);

有些疑问是自定义内容,虽然有自己的理解,但依然感觉较为抽象,后续会通过实践来理解其中的精妙之处。

领域驱动设计指引

  1. 领域驱动设计 作为一种软件开发方法,提供了战略上(思考方式) 和 战术上(落地方式) 的建模工具来帮助我们 设计高质量的软件模型;

  2. 领域驱动设计 不是关于技术的,而是关于讨论、聆听、理解、发现业务价值 的,目的是将 知识 集中起来,形成 通用语言(Ubiquitous Language);

  3. 在 领域驱动设计 中,技术也重要,但更重要的是要掌握 领域建模 中更高层次的概念;

领域建模

在领域中构建模型,什么是 领域模型?

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

为什么要使用DDD

Vaughn Vernon(沃恩.弗农) 在他的书里(《实现领域驱动设计》)阐述了很多,总结3点:

  1. 领域专家、开发者、业务人员等都掌握同样的软件知识,大家都使用相同的语言进行交流,每个人互相理解彼此在说什么;

  2. 设计就是代码,代码就是设计,软件能够表达大家所理解的意思;

  3. DDD持续关注业务,会产生一些业务价值

    1. 获得一个有用的 领域模型;

    2. 业务得到更准确的 定义和理解;

    3. 领域专家可以为领域设计做出贡献;

    4. 更好的用户体验(软件本身容易上手,减少培训,提高效率)

    5. 清晰的模型边界

    6. 良好的企业架构

    7. 敏捷、迭代式和持续建模

    8. 使用 战略和战术工具

DDD推进过程中存在的挑战

  1. 创建通用语言(所有人达成一致的某个业务术语的统一认知)消耗额外的时间和精力

  2. 持续的将领域专家引入项目

  3. 改变开发者 对领域的思考方式

如何使用DDD

DDD 提供了战略上 和 战术上 的建模工具来帮助我们 设计高质量的软件模型,战略设计 侧重于高层次、宏观上去划分和集成限界上下文,而 战术设计 则关注更具体使用建模工具来细化上下文。

战略上

  1. 限界上下文(Bounded Context) 为团队创建一个建模边界;

  2. 成员在边界内部为特定的 业务领域 创建 解决方案;

  3. 单个限界上下文中团队成员共享一套 通用语言(Ubiquitous Language);

  4. 不同团队各自负责一个限界上下文,可以使用 上下文映射图 对限界上下文进行 界分和集成;

战术上

  1. 实体(Entity)

  2. 值对象(Value Object)

  3. 聚合(Aggregate)

  4. 领域服务(Domain Service)

  5. 领域事件(Domain Event)

  6. 资源库(Repository)

下边我针对 战略和战术 两个方面进行讲解

DDD之战略

领域

广义上讲:领域 是一个组织所做的事情以及其中所包含的一切(为某个组织开发软件时,你面对的就是这个组织的 领域)。

软件开发中:领域 既可以表示 整个业务系统,也可以表示系统中的 核心域 或者 支撑子域。

在DDD中:领域 被划分为若干 子域,领域模型 在 限界上下文 中完成开发。

领域 包括下边三种 子域:

  1. 核心域(业务成功的主要促成因素,是企业的核心竞争力,应该给予最高的优先级、最资深的领域专家和最优秀的开发团队,实施DDD的过程中主要关注于 核心域);

  2. 支撑子域(不是核心,对应业务的 某些重要方面,有时我们会创建或者购买某个支撑子域);

  3. 通用子域(不是核心,但被整个业务系统所使用);

现实世界中的领域

现实世界中的领域包括 问题空间(Problem Space)和 解决方案空间(Solution Space):

  • 问题空间:是核心域和其他子域的组合,思考的是 业务面临的挑战

  • 解决方案空间:一组特定的 软件模型,包括一个或多个限界上下文,思考的是如何实现软件(限界上下文 即是一个 特定的解决方案,通过软件的方式实现解决方案)以 解决这些业务挑战

限界上下文

  • 一个 显式的边界(主要是一个语义上的边界),领域模型便存在于这个边界之内;每一个模型概念(包括它的属性和操作)在边界之内都具有特殊的含义;

  • 一个 给定的业务领域 会包含多个限界上下文,想与一个限界上下文沟通,则需要通过显示边界进行通信;系统通过确定的限界上下文来进行解耦,而每一个上下文内部紧密组织,职责明确,具有较高的内聚性;

  • 一个很形象的隐喻:细胞质所以能够存在,是因为细胞膜限定了什么在细胞内,什么在细胞外,并且确定了什么物质可以通过细胞膜(引用);

与技术组件保持一致

将限界上下文想象成技术组件是可以的,但是技术组件并 不能来定义(是说不能定义概念?) 限界上下文,有几种做法:

  1. 在使用 IntelliJ IDEA 时,一个 限界上下文 通常就是一个工程项目;

  2. 在使用Java时,顶层包名通常表示 限界上下文中顶层模块 的名字;

  3. 一个团队,一个限界上下文(即便项目按分层架构模块划分,团队依然应该只工作在一个限界上下文中);

上下文映射图

确定了单个限界上下文之后,有时还需要确定多个限界上下文之间的关系,这时就需要上下文映射图

一个项目的 上下文映射图 可以用两种方式来表示:

  1. 画一个简单的框图来表示 两个或多个 限界上下文 之间的 映射关系(该框图表示了不同的限界上下文在 解决方案空间 中是如何通过集成相互关联的);

  2. 通过 限界上下文 集成的源代码实现来表示;

限界上下文界分和集成

康威定律 告诉我们,系统结构 应尽量与 组织结构 保持一致

  • 这里认为团队结构(无论是内部组织还是团队间组织)就是 组织结构,限界上下文 就是 系统结构;

  • 因此,团队结构 应该和 限界上下文 保持一致。

梳理清楚上下文之间的关系,从 团队内部 的关系来看,有如下好处:

  1. 任务更好拆分(一个开发人员可以全身心的投入到相关的一个单独的上下文中);

  2. 沟通更加顺畅(一个上下文可以明确自己对其他上下文的依赖关系,从而使得团队内开发直接更好的对接);

从 团队间 的关系来看,明确的上下文关系能够带来如下帮助:

  1. 每个团队在它的限界上下文中能够更加明确自己领域内的概念(因为限界上下文是领域的 解决方案空间);

  2. 对于限界上下文之间发生交互,团队与限界上下文的一致性,能够保证我们明确对接的团队和依赖的上下游;

限界上下文之间的映射关系

  • 合作关系(Partnership):两个限界上下文建立起来的一种 紧密合作关系,要么一起成功,要么一起失败;

  • 共享内核(Shared Kernel):两个限界上下文紧密依赖共享的 部分模型和代码;

  • 客户方-供应方开发(Customer-Supplier Development):两个限界上下文有计划的 产生相互依赖(当两个团队处于上下游关系时,下游团队开发会受到上游开发的影响,上游团队计划应该估计下游团队的需求);

  • 遵奉者(Conformist):下游限界上下文只能 盲目依赖 上游限界上下文的现象;

  • 防腐层(Anticorruption Layer):一个限界上下文通过转换和翻译与其他的限界上下文进行交互;

  • 开放主机服务(Open Host Service):定义一种协议,让其他限界上下文通过该协议对本限界上下文进行访问;

  • 发布语言(Published Language):两个限界上下文之间翻译模型所需要的公用语言,通常与开放主机服务一起使用;

  • 另谋他路(Separate Way):两个限界上下文之间不存在任何关系,寻找另外更简单、更专业的方法来解决问题;

  • 大泥球(Big Ball of Mud):混杂在一起的、边界非常模糊的限界上下文关系;

领域/上下文划分的原则

在划分的过程中,经常纠结的一个问题是:这个模型(概念或数据)看起来放这个领域合适,放另一个也合适,如何抉择 呢?

  1. 依据该模型与边界内其他模型或角色 关系的紧密程度(比如,是否当该模型变化时,其他模型也需要进行变化;该数据是否通常由当前上下文中的角色在当前活动范围内使用);

  2. 服务边界内的 业务能力职责应单一,不是完成同一业务能力的模型不放在同一个上下文中;

  3. 划分的子域和服务需满足 正交原则(模块的独立性,领域名字代表的自然语言上下文保持互相独立);

  4. 组织中 业务部分的划分 也是一种参考(组织架构,一个业务部门的存在往往有其独特的业务价值);

简单打个比方,同一个领域上下文中的模型要保持 近亲关系,五福以内,同一血统(业务)。

DDD之战术

实体

当一个对象由其 唯一的身份标志 区分、具有可变的特性,这种对象即为实体。

  • 实体属性的验证可以放在实体内部进行

值对象

将领域概念建模成 值对象 的时候,应该将通用语言考虑在内,这是前提。(为什么将通用语言考虑在内?值对象 是领域里的一个概念,大家要统一认知,用通用语言作为标准,可以达到共同理解的目的)

构建值对象,要了解 值对象 以下的特点:

  1. 它度量或者描述了 领域中的一件东西;

  2. 它可以作为 不变量;

  3. 它将不同的相关的属性组合成一个 概念整体;

  4. 当度量和概念改变时,可以用另一个值对象予以 替换;

  5. 它可以和其他值对象进行相等性 比较;

  6. 它不会对协作对象造成任何副作用;

在实践中,需要保证值对象创建后就不能被修改,即不允许外部再修改其属性(如:在订单上下文中如果你只关注下单时商品信息快照,那么将商品对象视为值对象是很好的选择)

聚合

  • 聚合(Aggregate)是一组相关对象的集合,作为一个整体被外界访问,它由 实体 和 值对象 在 一致性边界之内 组成,聚合根(Aggregate Root)是这个聚合的根节点。

聚合的设计原则

  1. 在设计聚合时,我们需要慎重的考虑 一致性

    1. 关注聚合的 一致性边界,在一致性边界之内建模真正的 不变条件(不变条件 指的是业务规则)

      1. 同一个事务之内不能修改多个 聚合实例

    2. 在边界之外使用 最终一致性

  2. 设计 小聚合;(“小” 的极端意思是指 一个聚合只拥有全局标识和单个属性;这种做法不推荐)

  3. 通过唯一标识来引用其他聚合或实体:当存在对象之间的关联时,建议引用其唯一标识而非引用其整体对象(如果是外部上下文中的实体,引用其唯一标识或将需要的属性构造值对象)

注:如果聚合创建复杂,推荐使用 工厂方法 来屏蔽内部复杂的创建逻辑

领域服务

  • 当领域中的某个操作过程或转换过程不是实体或者值对象的职责时,将该操作放在一个单独的接口中,即 领域服务。

领域服务的特点

  1. 领域服务 和通用语言一致,表示 无状态 的操作,它用于实现特定于某个领域的任务;

  2. 某个操作不适合放在实体(聚合)与值对象上时,适合 领域服务;

    1. 执行一个显著的业务操作过程;

    2. 对领域对象进行转换;

    3. 以多个领域对象作为输入进行计算,结果产生一个值对象;

  3. 领域服务 是用来处理业务逻辑的(我们不能将业务逻辑放到应用层,即使非常简单,它依然是业务逻辑)

领域事件

  • 对 领域中 所发生的事件(领域专家所关心的发生在领域中的一些事件)进行建模,即 领域事件(领域模型 的组成部分)

领域事件的特点

  1. 领域事件 用来捕获领域中发生的一些事情,开始使用领域事件时,要 对不同的事件进行定义;

  2. “当...时,请通知我” 等等场景

领域事件发布方法

  1. 限界上下文内,观察者模式 是一种简单高效的发布领域事件的方法;

  2. 限界上下文外,利用 消息机制 将本地限界上下文产生的事件发送到 远程限界上下文 中(我们要保证 所有限界上下文 的最终一致性);

资源库

  • 对领域的存储和访问进行统一管理的对象,即 资源库(Repository);

资源库的特点

  1. 通常我们将 聚合实例 存放在资源库中,之后再通过资源库获取相同的实例;

  2. 通常来说,聚合类型 和 资源库之间存在着 一对一的关系;

    1. 当两个或多个聚合位于同一个对象层级中时,他们可以共享同一个资源库;

注:资源库和 DAO是不同的,一个DAO主要从数据库表的角度来看待问题,并且提供 CRUD 操作

DDD之架构

极简化架构设计主要从下边三个角度出发:

  • 业务架构:根据业务需求设计业务模块及交互关系;

  • 系统架构:根据业务需求设计系统和子系统的模块;

  • 技术架构:根据业务需求决定采用的技术及框架;

DDD的核心诉求 就是能够让 业务架构 和 系统架构 形成绑定关系,从而当我们去响应 业务变化 调整业务架构时,系统架构的改变是随之自发的

这个 业务变化 的结果有两个:

  1. 业务架构 的梳理和 系统架构 的梳理是同步渐进的,其结果是划分出的 业务上下文 和 系统模块结构 是绑定的;

  2. 技术架构 是解耦的,可以根据划分出来的业务上下文的系统架构选择最合适的实现技术;

架构类型

分层架构
  • 所有架构的始祖,支持N层架构系统,将一个应用程序或者系统分为不同的层次

DDD使用的传统分层架构:

分层架构原则:每层只能与其下方的层发生耦合(分层架构 分为 严格分层架构 和 松散分层架构)。

严格分层架构:每层只能和直接位于其下方的层发生耦合。

松散分层架构:任意上方层与任意下方层发生耦合。

六边形架构

六边形结构(端口与适配器架构、onion架构):一种具有对称性特征的架构风格。

为什么是6边形?不是4边形或8边形?

六边形架构视角:架构中存在两个区域,“外部区域”和“内部区域”,外部区域 供给客户提交输入,内部区域 获取持久化数据、对数据进行存储或转发。

面向服务架构

服务设计原则

  1. 服务契约:通过契约文档,服务阐述自身的目的与功能;

  2. 松耦合:服务将依赖关系最小化;

  3. 服务抽象:服务只发布契约,隐藏内部逻辑;

  4. 服务重用性:一种服务可被其他所有服务重用;

  5. 服务自治性:服务自行控制环境与资源以保持独立性;

  6. 服务无状态性:服务负责消费方的状态管理;

  7. 服务可发现性:客户可通过服务元数据来查找服务和理解服务;

  8. 服务组合性:一种服务可用由其他服务组合而成,不用管其他服务的大小和复杂性如何;

参考资料

  1. 《实现领域驱动设计》

  2. 领域模型的应用 相关文章

  3. 领域驱动设计实践