领域驱动设计(DDD)思想笔记

引言

在软件开发的领域中,领域驱动设计(DDD)是一种重要的设计方法论。本篇笔记基于Eric Evans的原著和孙向晖、霍泰稳的中文翻译,对DDD的核心思想进行梳理和总结,并向所有作者致敬。

不要畏惧你所不知道的领域,如果你有需要,那么就搞定它。

  • 刘晓成 @小诚信驿站

一、领域驱动设计的定义

领域驱动设计是一种以业务为中心的设计方法,它强调软件开发架构师和开发人员与业务专家紧密合作,共同构建出能够解决实际业务问题的领域模型。

二、DDD的核心

DDD的核心在于创建精简且与实现紧密匹配的业务模型。这种模型不仅需要反映业务需求,还要能够指导技术实现。

三、为什么需要DDD?

面向对象和面向过程的程序设计已经存在,但DDD提供了一种更高层次的抽象和封装,帮助解决以下问题:

  • 精简业务模型:确定哪些对象对系统有用。
  • 克制系统边界:去除无用的对象,明确需求边界。
  • 适度不蔓延,不泛化:保证模型对象的适用性。
  • 封装边界问题:通过高层次的封装,降低系统复杂度。
  • 业务与研发思维问题:解决开发人员对业务理解不足的问题。

四、DDD方法论### 4.1 理解你的领域,熟悉你的业务技术实现应围绕业务场景进行。在系统设计前,必须充分理解业务领域的核心概念和元素,并精确实现它们之间的关系。这需要:

  • 软件建模:对领域进行建模,反映业务需求。
  • 软件抽象:对领域进行抽象,保留关键元素。
  • 领域交流:领域专家与技术专家共享知识和信息。

4.2 构建领域知识,实践你的业务构建业务领域知识体系需要从基础开始,最好的方式是与领域专家进行交流。这包括:

  • 与专家交谈:学习领域知识,构建领域模型。
  • 初期领域建模:根据专家描述勾勒领域骨架视图,虽不完美,但为起点。

结语领域驱动设计是一种深入业务、强调模型与实现一致性的设计方法。通过DDD,我们可以更好地理解业务,构建高质量的软件系统,满足不断变化的业务需求。


在软件开发过程中,构建一个精确的领域模型是至关重要的。这通常涉及到软件架构师、开发人员和领域专家的紧密合作。以下是对上述内容的重新编写和结构化:

2. 构建领域模型的时间损耗和沟通

  • 多次沟通的重要性:在创建领域模型的过程中,尽管可能会耗费大量时间,但通过软件架构师、开发人员和领域专家之间的多次沟通,可以确保模型的准确性和有效性。这种沟通是实现目标建模准确性的关键步骤

4.3 模型驱动设计的实现

4.3.1 从模型到代码的转换方法

  • 分析模型出发:在模型驱动设计中,我们从分析模型开始,通过过程化编程(利用函数调用和数据结构算法)来表达模型。
  • 复杂模型的处理:对于复杂的模型,建议使用伪代码来实现,以避免过程化编程可能带来的问题。

4.3.2 模型驱动设计的基本构成要素

4.3.2.1 分层架构设计
  • 逻辑清晰的重要性:在模型驱动设计中,采用分层架构设计(如MVC模式)可以保证逻辑的清晰性和模块的独立性。 通过上述结构化的内容,我们可以看到,无论是在构建领域模型还是实现模型驱动设计时,沟通、分析和分层架构设计都是不可或缺的组成部分。


在软件开发中,MVC架构是一种常见的设计模式,它将应用程序分为三个主要部分:视图(View)、控制器(Controller)和模型(Model)。这种分层的方法有助于提高代码的可维护性和可扩展性。具体来说,MVC架构包括以下概念层:

  1. 视图层(View):负责展示数据和用户界面。它与用户直接交互,接收用户的输入并显示应用程序的数据。

  2. 控制器层(Controller):作为视图和模型之间的中介,处理用户的输入,调用模型层的逻辑,并选择合适的视图来展示结果。

  3. 服务层(Service):通常也被称作领域层,负责实现应用程序的业务逻辑。它处理复杂的业务规则,并且可以调用模型层来获取或更新数据。

  4. 数据访问对象层(DAO)/映射器层(Mapper):负责与数据库交互,执行CRUD(创建、读取、更新、删除)操作。 在MVC架构中,还有两种重要的概念:实体(Entity)和值对象(Value Object)。实体通常具有唯一标识符,表示具有生命周期的业务对象。而值对象则没有标识符,它们是不可变的,可以被轻易地创建和销毁。值对象通常用于表示简单的数据结构,如地址、日期等。 一个常见的误解是将值对象与DTO(数据传输对象)或VO(值对象)混淆。实际上,值对象是领域模型的一部分,而DTO或VO通常用于表示从实体或领域模型派生出的数据副本,主要用于数据传输。 总结来说,MVC架构通过分层的方式组织代码,而实体和值对象的概念则帮助我们更好地理解和设计领域模型。

4.3.2.3 服务(系统服务)

服务是领域模型中一种特殊的组件,它不应替代领域对象的常规操作。只有在操作成为领域中的关键概念时,才应建立相应的服务。 服务具有以下三个特征:

  1. 领域相关性:服务执行的操作涉及一个领域概念,该概念通常不局限于单一实体或值对象。

  2. 跨对象操作:服务执行的操作会涉及领域内多个对象的交互。

  3. 无状态性:服务的操作不依赖于任何持久状态。


4.3.2.4 模块(功能模块)

在大型复杂项目中,模型可能会变得过于庞大,难以整体讨论和理解其内部各部分之间的关系。此时,模块化是一种有效的组织方式,用于降低复杂性。 模块化将相关概念和任务组织在一起,帮助我们更好地管理和理解模型的不同部分。

4.3.2.5 聚合(对象的封装组合)

聚合是一种定义对象所有权和边界的领域模式。它在设计中用于处理对象的创建和存储问题,通常与工厂和数据库模式结合使用。 在模型中,众多领域对象会相互关联,形成复杂的网络。聚合模式帮助我们理解和管理这些关系。

工厂方法与抽象工厂模式工厂方法和抽象工厂模式专注于对象的创建过程,确保对象的不变量得到维护,并提供对对象的引用或其拷贝。

工厂方法工厂方法模式是一种创建型设计模式,用于定义创建对象的接口,让子类决定实例化哪一个类。工厂方法让类的实例化推迟到子类。

抽象工厂模式抽象工厂模式是一种创建型设计模式,提供一个接口以创建一系列相关或依赖对象,而不指定它们的具体类。

资源库模式资源库模式涉及对象从创建到销毁的整个生命周期管理,包括从数据库的读取、创建、更新、删除以及垃圾回收。

资源库的定义资源库封装了获取对象引用所需的所有逻辑。领域对象无需处理任何基础设施,从而保持模型的清晰和专注。

资源库的作用

  • 引用管理:资源库保存对对象的引用,便于对象的创建和检索。
  • 持久化解耦:资源库作为领域模型与持久化基础设施之间的中介,实现解耦。

资源库的策略资源库可能包含访问持久化存储的策略,例如:

  • 存储位置策略:对不同类型对象使用不同的存储位置。
  • 访问策略:基于特定策略访问持久化存储介质。

资源库的实现当客户程序请求一个对象时,资源库首先检查是否已保存该对象的引用。如果没有,资源库将从存储介质中获取对象。这样,领域模型与对象的持久化细节分离,提高了模型的可维护性和灵活性。

总结工厂方法和抽象工厂模式以及资源库模式都是设计模式中用于管理对象创建和生命周期的重要模式。它们通过封装创建逻辑和持久化细节,提高了代码的可维护性和可扩展性。

资源库的接口是纯粹的领域模型(比如根据业务场景实现的查找用户或者添加用户的功能)


在软件开发过程中,规约和模型的持续优化是至关重要的。以下是对上述段落内容的重新组织和阐述:

4.3.2.8 规约的复杂性与服务支持

在软件开发中,规约如API约定、接口文档和交互协议等,允许我们定义更为复杂的查询条件,以提供更加灵活的服务支持。

4.3.3 面向深层理解的重构

随着对业务的深入理解,我们发现初期的领域模型可能并不完美。因此,重构成为我们优化设计、适应业务变化的关键步骤。

4.3.3.1 持续重构的必要性

  • 设计方案的适应性:随着需求的变化,持续的重构能够帮助我们调整设计方案,以更好地适应这些变化。

4.3.3.2 凸显关键概念

在业务领域建模时,关键概念如约束、过程和规约,需要我们不断地完善,但它们本身不应频繁变动。

  • 约束:包括限制条件和校验参数,是确保业务逻辑正确性的基础。
  • 过程:实现业务逻辑的具体步骤。
  • 规约:约定的接口文档和交互方式,是系统间通信的基础。

4.3.3.3 保持模型一致性

为了确保不同服务系统与整体业务领域模型的一致性,我们需要:

  • 划清边界:明确每个服务系统的职能和应用上下文。
  • 持续集成:通过集成测试来检测模型的完整性,并确保概念的集成。
  • 上下文映射:使用文档或图表来展示不同上下文之间的关系和交互。 例如,分层领域需要界定应用上下文,而持续集成则应用于这些界定的上下文之中,不处理相邻上下文间的关系。 上下文映射(Context Map)是一种展示不同上下文及其关系的文档,可以是图表形式或其他书面形式。

结构化与条理性的要点

  1. 规约的复杂性:定义查询条件,提供服务支持。
  2. 重构的必要性:适应需求变化,优化设计方案。
  3. 关键概念的凸显:完善约束、过程和规约。
  4. 模型一致性的保持:确保服务系统与整体模型的一致性,通过划清边界、持续集成和上下文映射实现。 通过上述结构化的内容,我们能够更清晰地理解软件开发中规约和模型的重要性及其优化过程。

共享内核开发策略

目标与挑战共享内核旨在通过共享基础服务减少开发中的重复工作,同时保持两个独立开发团队各自的上下文独立性。

开发注意事项

  1. 团队协作:两个团队在修改共享内核时需要保持高度的协作。
  2. 代码整合:使用内核代码副本的团队应尽早进行代码的合并,推荐频率为每周至少一次。
  3. 自动化测试:利用测试工具确保每次内核修改都能迅速得到测试反馈。
  4. 变更通知:任何对内核的更改都应及时通知相关团队,确保信息同步。

团队间沟通

  • 密切沟通:团队之间需要保持紧密的沟通,以便了解最新功能和变更。
  • 共享信息:确保所有团队成员都能及时获取到内核的最新状态和功能更新。

结构性建议

  • 定期会议:设立定期的沟通会议,讨论内核的开发进展和问题。
  • 文档记录:详细记录内核的每次更改和测试结果,便于团队成员查阅。

总结共享内核的开发需要团队间的密切合作和高效的沟通机制。通过定期的代码合并、自动化测试和及时的信息共享,可以确保内核的稳定发展和功能的持续更新。

  • 客户和供应商—实际上是上下游系统

  • 顺从者(如何应对内部需求堆积,合理协配外部需求)

  • 防奔溃层(实际上指的是如何应对模型针对不同外部需求的服务变化可以用门面模式,适配层处理)

领域驱动设计(DDD)实践指南

一、独立方法与开放主机服务

领域驱动设计中,独立方法指的是将不相关的服务内容作为独立的服务进行部署和实现。这有助于降低系统的耦合度,提高各个服务的独立性和可维护性。 开放主机服务则强调提炼系统的核心服务和业务,通过精炼模型找到核心域,强调最有价值和特殊的概念,使核心域更加集中和易于管理。

二、实践DDD的方式

1. 购买现成的方案购买现成的DDD方案可以快速获得全套的设计和实现,但可能面临学习曲线陡峭和依赖特定技术栈的问题。此外,如果现成方案存在缺陷,可能需要等待供应商修复。

2. 外包将DDD的设计和实现外包给其他团队或公司,可以让你更专注于核心业务。但这也意味着需要处理外包团队的代码集成问题,以及保持与外包团队的持续沟通。

3. 利用已有模型借鉴市面上已有的分析模型作为子域设计的灵感来源。虽然直接复制可能不现实,但有些模型只需稍作修改即可适用于特定场景。

4. 自行实现自行实现DDD可以确保与现有系统的完美集成,但同时也意味着需要承担额外的开发和维护成本。

三、DDD的思维陷阱

1. 事必躬亲在DDD中,模型的建立需要通过代码来实现,避免过度依赖抽象而忽视了实际的编码工作。

2. 专注于具体场景抽象思维需要结合具体的业务场景进行落地,确保设计既具有理论支撑,又能满足实际需求。

3. 选择性应用DDD并非所有领域都适合应用DDD,应当根据实际情况画出范围表,决定哪些领域需要进行领域驱动设计,避免无谓的蔓延。

4. 持续实验与迭代将模型视为一个创造性的流程,通过不断的实验和迭代来优化设计,同时接受并从错误中学习。