领域驱动设计开发学习笔记

有机会在实际工作中使用 DDD ,持续更新下笔记。

说明:为了表达的简洁性,除非特殊说明,本文所用的 DDD 缩写不区分领域驱动设计及领域驱动开发。

简介

为什么需要 DDD?

应对软件系统复杂性

解决复杂和大规模软件的武器可以被粗略地归为三类:

  • 分治

把问题空间分割为规模更小且易于处理的若干子问题。分割后的问题需要足够小,以便一个人单枪匹马就能够解决他们。

其次,必须考虑如何将分割后的各个部分装配为整体。分割得越合理越易于理解,在装配成整体时,所需跟踪的细节也就越少。即更容易设计各部分的协作方式。评判什么是分治得好,即高内聚低耦合。

  • 抽象

使用抽象能够精简问题空间,而且问题越小越容易理解。举个例子,从北京到上海出差,可以先理解为使用交通工具前往,但不需要一开始就想清楚到底是高铁还是飞机,以及乘坐他们需要注意什么。

  • 知识

顾名思义,DDD 可以认为是知识的一种。

DDD 提供了这样的知识手段,让我们知道如何抽象出限界上下文以及如何去分治。

适用于微服务

DDD 的核心诉求就是将业务架构映射到系统架构上,在响应业务变化调整业务架构时,也随之变化系统架构。
而微服务追求业务层面的复用,设计出来的系统架构和业务一致;在技术架构上则系统模块之间充分解耦,可以自由地选择合适的技术架构,去中心化地治理技术和数据。

  • 关注点分离

DDD 和微服务都强调创建一个高内聚、低耦合对象,DDD 有限界上下文,微服务有业务边界,DDD 的限界上下文类似于一个微服务进程。

  • 业务视角

系统分治一般从两个维度去执行:技术维度和业务维度。微服务架构和 DDD 都更强调从业务维度去做分治来应对系统复杂度。

什么是 DDD?

是一种通过将实现连接到持续进化的模型来满足复杂需求的软件开发方法。

什么是 MDD?

Model Driven Design,强调的是模型与实现不能分开。

DDD 的优点?

DDD 的缺点?

DDD 的应用场景?

如何进行 DDD?

领域驱动设计的前提

  • 把项目的主要重点放在核心领域(core domain)和域逻辑
  • 把复杂的设计放在有界域(bounded context)的模型上
  • 发起一个创造性的合作之间的技术和域界专家以迭代地完善的概念模式,解决特定领域的问题

设计领域模型的一般步骤

  • 根据需求划分出初步的领域和限界上下文,以及上下文之间的关系
  • 进一步分析每个上下文内部,识别出哪些是实体,哪些是值对象
  • 对实体、值对象进行关联和聚合,划分出聚合的范畴和聚合根
  • 为聚合根设计仓储,并思考实体或值对象的创建方式
  • 在工程中实践领域模型,并在实践中检验模型的合理性,倒推模型中不足的地方并重构

核心概念

架构设计分类

  • 业务架构:根据业务需求设计业务模块及其关系。

  • 系统架构:设计系统和子系统的模块。

  • 技术架构:决定采用的技术及框架。

战略建模

战略和战术设计是站在 DDD 的角度进行划分。战略设计侧重于高层次、宏观上去划分和集成限界上下文,而战术设计则关注更具体使用建模工具来细化上下文。

理想情况下,只有一个统一的模型。但是通常情况下都无法实现,因此在实践中通常分成多个模型。认识这个事实并并且准守它对实践是非常有益的。策略设计的目的是设计一套原则用于是维护模型完整性,提升领域模型和使用多个模型。

限界上下文

任何大型项目都有多个模型。 然而,当基于不同模型的代码相结合,软件变得越来越多,不可靠,并且难以理解。 团队成员之间的交流变得越来越难。模型的使用情境变得越来越不清晰。

因此:需要明确定义模型适用的上下文,并且根据团队组织,应用程序特定部分的使用情况以及代码库和数据库模式等物理表现明确设置边界。保持模型在这些范围内严格一致,并且不被外部的问题影响。

上下文

词或句子出现的位置。

划分限界上下文

我们不应该按照技术架构或者开发任务来创建限界上下文,应该从需求出发,按照语义的边界(领域)来考虑划分。

  • 提取术语、寻找联系
  • 描述职责
持续集成

当愈多人在相同的有限背景下工作时,模型就愈应该分裂。 团队越大,问题就越大,即使只有三四个人也会遇到严重的问题。 然而,将系统分解为更小的环境最终会失去一个有价值的集成和一致性。

因此:创建一个经常合并所有代码和其他实现工件的过程,用自动化测试快速标记碎片。通过持续地运用通用语言去夯实随着概念在不同人的头脑中的演变而逐渐形成对模型的共同观点。

上下文映射图

在缺乏全局认识的情况下,个别有界上下文会留下一些问题。其他模型的背景可能仍然是模糊不清的。其他团队的人不会意识到上下文的界限,并且会不知不觉地做出模糊边缘或使连接复杂化的变化。 当连接必须在不同的上下文之间进行时,它们往往会相互渗透。

因此:确定项目中正在使用的每个模型并定义其有界的上下文。这包括非面向对象子系统的隐式模型。命名每个有界的上下文,并将其命名为通用语言的一部分。描述模型之间的关联点,确保任何用于共享交流的词语都有清晰明确的含义,映射现有的情形。

(领)域

一类知识(ontology),影响,或活动。软件使用的场景。

现实世界中,领域包含了问题域和解系统。一般认为软件是对现实世界的部分模拟。在 DDD 中,解系统可以映射为一个个限界上下文,限界上下文就是软件对于问题域的一个特定的、有限的解决方案。

模型

一类描述域的不同方面并可用于解决相关问题的系统化的抽象。

统一术语/Ubiquitous Language

一种团队成员使用,为了描述域模型而构造的语言。

康威定律

任何组织在设计一套系统时,所交付的设计方案在结构上都与该组织的沟通结构保持一致。(梅尔·康威)

(领)域对象

就是领域中出现的可以用于准确描述现实问题的模型/对象,可以是实体、值对象、资源库、服务、模块、工厂、聚合、领域事件等。

在 DDD 中,有表示、创建和检索领域模型的工件:

实体

一个不由自身属性定义而是由标识线和它的身份定义的对象。

例如:大多数航空公司在每次航班上都独特地区分每个座位。每个席位都是在这种情况下的一个实体。

不过,西南航空,EasyJet 和瑞安航空并没有区分每个座位;所有的座位都是一样的。在这种情况下,一个席位实际上是一个价值对象。

(价)值对象

只包含元素属性的不可变对象。

例如:当人们交换名片时,他们一般不会区分每张独特的名片;他们只关心印在卡片上的信息。在这种情况下,名片是 Value Object。

聚合/Aggregate

一组相关对象的集合,作为一个整体被外界访问。聚合根是这个聚合的根结点。

聚合根

由 ROOT ENTITY 绑定在一起的对象的集合,也称为聚合根。聚合根通过禁止外部对象保持对其成员的引用来保证在聚合内进行的更改的一致性。

例如:驾驶汽车时,不必担心向前移动车轮,使发动机燃烧火花和燃料等。你只是在开车。在这种情况下,汽车是其他几个对象的集合,并作为所有其他系统的聚合根。

领域事件

一个域对象定义了一个事件。域事件是域专家所关心的事件。

资源库

对于检索特定域对象的方法应该委派给 Repository 对象,因为这样可以很容易地互换替代存储的实现。

工厂

对于那些需要创建特定域对象的方法应该委派给工厂对象,因为这样可以更容易的替换实现。

模块

一种表达机制,划分代码和概念。

服务

强调与其他对象的关系,只定义了可以为客户做什么,不应该替代 ENTITY 和 VALUE OBJECT 的所有行为。

应用服务

应用服务是用来表达用例和用户故事(User Story)的主要手段。
应用层通过应用服务接口来暴露系统的全部功能。在应用服务的实现中,它负责编排和转发,它将要实现的功能委托给一个或多个领域对象来实现,它本身只负责处理业务用例的执行顺序以及结果的拼装。通过这样一种方式,它隐藏了领域层的复杂性及其内部实现机制。
应用层相对来说是较“薄”的一层,除了定义应用服务之外,在该层我们可以进行安全认证,权限校验,持久化事务控制,或者向其他系统发生基于事件的消息通知,另外还可以用于创建邮件以发送给客户等。
应用层作为展现层与领域层的桥梁。展现层使用VO(视图模型)进行界面展示,与应用层通过DTO(数据传输对象)进行数据交互,从而达到展现层与DO(领域对象)解耦的目的。

领域服务

领域层就是较“胖”的一层,因为它实现了全部业务逻辑并且通过各种校验手段保证业务正确性。而什么是业务逻辑呢?业务流程、业务策略、业务规则、完整性约束等。
当领域中的某个操作过程或转换过程不是实体或值对象的职责时,我们便应该将该操作放在一个单独的接口中,即领域服务。请确保该服务和通用语言时一致的;并且保证它是无状态的。

领域服务的“无状态”怎么理解?

领域服务是用来协调领域对象完成某个操作,用来处理业务逻辑的,它本身是一个行为,所以是无状态的。状态由领域对象(具有状态和行为)保存。

什么时候使用领域服务?

因为领域对象是具有状态和行为的,所以我们也可以通过实体或值对象来处理业务逻辑。一般来说,在下面的几种情况下,我们可以使用领域服务:

  • 执行一个显著的业务操作过程
  • 对领域对象进行转换
  • 以多个领域对象为输入,返回一个值对象

数据流转/DTO

防腐层

FAQ

领域服务和应用服务的联系与区别?

服务指的都是行为的抽象,它们的区别是从 DDD 的分层架构角度去说的:领域服务属于领域层,表述领域的行为;应用服务属于应用层,表示应用的行为。

那怎么理解应用行为和领域行为呢,应用行为描述了一个具体操作从开始到结束的每一个环节,而领域行为是对应用行为的细化,用来处理具体的某一个环节。
比如,我们手机购物,从购物车结算这一场景来举例,这就是一个应用行为。而这个应用行为又主要包括金额计算、支付、生成订单,这些子环节就可以理解为一个领域行为。

当应用服务中的逻辑趋于复杂时,我们就要小心领域逻辑泄露到应用服务中去。而在使用领域服务时,我们又要避免过度使用,因为会导致贫血领域模型。毕竟有些单一的操作更适合放到领域对象(实体和值对象)中去。

总结:

  • 服务是行为的抽象。
  • 应用服务通过委托领域对象和领域服务来表达用例和用户故事。
  • 领域对象(实体和值对象)负责单一操作。
  • 领域服务用于协调多个领域对象共同完成某个业务操作。
  • 应用服务不处理业务逻辑,领域服务处理业务逻辑。

应用层和服务层有什么区别?

Application 负责展现层与领域层之间的协调,协调业务对象来执行特定的应用程序任务,应用层不包含业务逻辑

Domain 负责表达业务概念,业务状态信息以及业务规则,是业务软件的核心。

如何划分领域模型?

参考