Skip to main content

领域驱动设计简介

为什么需要 Domain-driven design(DDD)

我很看重事物发展过程的“自然”面。

把一组随机数交给一个中学生排序,估计他也是能琢磨出一种排序算法的,此时的排序算法很可能就是我们在大学里才学到的直接排序法。

很多人也都在程序中创建过一些服务类,并且将事务边界放在这些类的方法上,那么此时,我们所创建的便是DDD所称为的应用服务(Application Service)。

于是我们看到,很多知识都可以通过我们自己的自然逻辑思考而获得。但是,我们和那些大牛的区别在于,他们有能力将这些散乱的知识概念化、理论化、抽象化和系统化。

我们从传统架构直接飞跃到互联网领先的微服务架构,是巨大的挑战,我们需要一个指导思想引导我们去进行转型,而不能像从前一样没有规范去约束、没有共同情景意识去沟通、没有设计就开发。否则,微服务维护将会变得异常昂贵、晦涩难懂。

代码的质量如果不加以控制,就一定会迅速腐烂变质。这是一个客观规律,就像在热力学第三定律中,熵总是会增加一样。对于软件开发而言,“概念完整性”就相当于热力学第三定律中的熵,是衡量软件项目混乱程度的重要指标。DDD 就是目前维护软件项目“概念完整性”的最佳良药

概述

它是一套应对复杂软件系统分析和设计的面向对象建模的方法论。

Domain-driven design (DDD) advocates modeling based on the reality of business as relevant to your use cases. In the context of building applications, DDD talks about problems as domains. It describes independent problem areas as Bounded Contexts (each Bounded Context correlates to a microservice), and emphasizes a common language to talk about these problems. It also suggests many technical concepts and patterns, like domain entities with rich models (no anemic-domain model), value objects, aggregates, and aggregate root (or root entity) rules to support the internal implementation

领域驱动设计(DDD)主张根据与你的用例相关的现实业务进行建模。在构建应用程序的背景下,DDD将问题视为领域。它将独立的问题领域描述为限界上下文(每个限界上下文都与一个微服务相关),并强调用通用语言来谈论这些问题。它还提出了许多技术概念和模式,例如具有丰富模型的领域实体(非贫血模型)、值对象、聚合以及聚合根(或根实体)规则以支持内部实现。

Under domain-driven design, the structure and language of software code (class names, class methods, class variables) should match the business domain.

在领域驱动的设计下,软件代码的结构和语言(类名、类方法、类变量)应该与业务领域相匹配。

简要来说,它就是业务领域的OOP。它将业务进行抽象,进而明确各个对象间的关系、职责,是微服务划分、业务架构设计的指导思想。

业务与技术的关系

DDD可辅助我们理解系统的复杂性,我们可从业务和技术的两个角度出发进行设计。以DDD术语来说,就是战术设计战略设计。从业务上对需求模块进行划分,即是在将业务领域划分为子域。在技术上进行的项目代码的划分,即是在划分限界上下文。 简单梳理一下,领域需要划分,限界上下文也需要划分。一个是业务上抽象的划分,一个是技术上,开项目的实际划分,即抽象与实际。

领域是一整个系统,领域划分多个子域,子域按不同角色、重要程度分为核心域、支撑子域、通用子域。

支撑子域是支撑其他子域去实现自身作用的(一般它需要被依赖)(也可以同时是核心域) 通用子域是基本上很多子域都得使用的(比如身份访问控制子域),更确切的说,它能被其他领域模型使用,即一个优秀的通用子域划分,不仅在这个领域也能使用,放到其他的领域也能够完美的复用。可以使用现成的解决方案实现。

在没有进行DDD设计下,比如一个零售系统:分为电子商务系统、库存系统、外部的预测系统等等,这时候,限界上下文就是各个系统(一般来说就是一个解决方案中的一个工程项目)。

而这时候对其进行领域划分,划分子域,可以发现子域和限界上下文的对应关系非常混乱。

一个好的DDD,应要尽量将子域的划分与限界上下文重合起来。

所以,谈论领域是业务上的概念,而限界上下文是技术中具体实现的概念,将领域的划分落地的概念,他们关系即抽象与具象,业务与技术。 开发人员需要与领域专家一起来进行领域的划分,一起进行领域模型的建模,先有抽象,才有具象。(领域专家其实就是业务专家)

DDD分层架构最佳实践

术语

DDD的“通用语言”。

属于业务领域的名词:

名词英文备注
领域Domain领域囊括一整个系统,领域划分多个子域,子域按不同角色、重要程度分为核心域、支撑子域、通用子域。 将问题视为领域,称为“问题空间”(problem space),对问题空间的开发将产生新的核心域,问题空间的评估应该同时考虑已有的子域和额外所需子域。
领域模型Domain model一说是指整个领域的划分,是为领域模型。 个人倾向于理解为“领域中的模型”,子域内的业务的抽象建模,包含它的定义、属性、功能。它的实例称为领域对象
核心子域Core or Basic Sub-domain核心业务划分出来的子域。
支撑子域Auxiliary or Support Sub-domain支撑其他子域去实现自身作用的(一般它需要被依赖)(也可以同时是核心域)
通用子域Generic Sub-domain基本上很多领域都会使用的(比如身份访问控制子域),通用的子域划分。
通用语言Ubiquitous Language通用语言是由整个团队共同创建的一门语言,其中包括领域专家、开发者、业务分析员等。 使用通用语言描述系统,使得整个团队拥有共同的上下文、情景意识,不再沟通困难。 可以是一组术语、一个简单的用例场景。
领域事件Domain Events领域中发生的事情。 通常用名词+动词命名,动词为过去分词形式。如UserPasswordChanged

属于技术领域的名词:

名词英文备注
限界上下文Bounded Context技术上的服务划分,去实现业务上的领域划分,简单来说就是解决方案中的一个工程项目。限界上下文中的空间划分称为“解决方案空间”(solution space)。 我认为,微服务诞生之后,限界上下文可以认为就是划分出的一大类微服务。
领域模型Domain model开发人员眼中的领域模型,需要具象化,重新分成实体、值对象、聚合
实体Entity可以认为是ORM中的实体,基本上都是需要持久化维护的数据。 它相对于值对象的最大区别是,它可以进行修改。
值对象Value object构造函数创建值对象后,它的属性就不可被修改了。它相对于实体更加简单,因此尽量将领域模型映射到值对象。
聚合Aggregate聚合是值对象、实体在同一个限界上下文之内的组合。聚合根是一个聚合的根部,一个类,它必定也同时是一个实体。 聚合的诞生是因为需要划分事务的边界(一致性问题),一个聚合可以是一个单一的实体。所以,不会有游离的实体。
充血模型Rich domain model好的领域模型设计。低耦合,高内聚。 它不仅含有属性,还有相关方法,成功的OOP。
贫血模型Anaemic domain model坏的领域模型设计。 仅仅只是属性定义,没有什么方法。不用于领域模型设计,一般用于DTO等战术设计
DDD的分层架构DDD的通用架构包含4个概念层: 展现层、应用层、领域层、基础设施层
应用层Application layer很薄的一层,用来协调应用的活动。它不包含业务逻辑。它也 不保留业务对象的状态,但它保留有应用任务的进度状态
领域层Domain layer本层包含关于领域的信息。这是业务软件的核心所在。在这里 保留业务对象的状态。对业务对象和它们状态的持久化被委托 给了基础设施层
基础设施层Infrastructure layer本层作为其他层的支撑库存在。它提供了层间的通信,实现对 业务对象的持久化,包含对用户界面层的支持库等作用
应用服务Application service它位于DDD分层架构的应用层。 应用服务是无状态的。 给外部使用的服务,里面没有具体的业务逻辑,只是资源库、聚合、领域服务、值对象的简单运用。
领域服务Domain service它位于DDD分层架构的领域层。 领域服务也是无状态的。 领域服务相对于应用服务、通用服务的区别是,它充满了业务逻辑。 不应把所有业务逻辑都拿到领域服务,因为这样会导致领域模型变成贫血模型。
上下文映射图Context mapping diagram对限界上下文进行细致的图形描述,并有相应规范的标注。
上游/下游Upstream [U] Downstream [D]【Upstream-Downstream Relationship】上游是下游的基础上下文,即下游依赖于上游。通过上游[OHS,PL]与下游[ACL]解耦
开放主机服务Open Host Service [OHS]定义的一种协议,让其他服务通过协议访问本服务。可以理解为API
发布语言Published Language [PL]在两个限界上下文之间翻译模型需要的一种共用语言,可以认为就是DTO
防腐层Anticorruption Layer [ACL]可以认为是适配器,转换/翻译不同的模型。不过,这里的防腐层概念更细,它是指两个不同限界上下文之间的翻译层。
共享内核Shared Kernel [SK]【Symmetric Relationship】对模型和代码的共享将产生紧密的依赖性,但该设计时好时坏。(下游Domain、上游Domain依赖同一个SKDomain)

共享内核应尽量保持小型化、定义显式边界。
大泥球Big Ball of Mud已有的系统(未遵循DDD的),或难以划分的模型,这类都归于大泥球。
合作关系Partnership [P]【Symmetric Relationship】两个限界上下文要么一起成功,要么一起失败。(相互依赖,定义公用接口,互相需要调用双方,即互为上下游关系)
客户方-供应方开发Customer Supplier Development [C] [S]【Upstream-Downstream Relationship】上游团队顾及下游团队的开发,即[S]是供应方,需要为客户方提供特定接口。因为一般情况下,上游团队可以不顾及下游,因为上游感知不到下游。
遵奉者Conformist [CF]下游团队直接使用上游团队的模型、语言(即下游Domain直接依赖上游Domain)

战略设计(Strategic Design)

划分领域

During the strategic phase of domain-driven design (DDD), you are mapping out the business domain and defining bounded contexts for your domain models.

战术设计(Tactical Design)

分析领域模型

Tactical DDD is when you define your domain models with more precision. The tactical patterns are applied within a single bounded context.

通用语言(Ubiquitous Language)

领域专家和开发人员应建立并使用通用语言进行交流。

可以是一组术语、一个简单的用例场景。

领域事件

领域中发生的事情。

通常用名词+动词命名,动词为过去分词形式。如UserPasswordChanged

应用服务

它位于DDD分层架构的应用层

应用服务是无状态的。

给外部使用的服务,里面没有具体的业务逻辑,只是资源库、聚合、领域服务、值对象的简单运用。

注意,业务逻辑是指非常具体的逻辑,比如判断一个航班是否是热点航班。显然这个是聚合中的功能,而非应用服务要做的事情。

应用服务运用已经建模好的一些对象,不是所说的业务逻辑。

领域服务

它位于DDD分层架构的领域层。

领域服务是无状态的。

领域服务相对于应用服务、通用服务的区别是,它充满了业务逻辑。

不应把所有业务逻辑都拿到领域服务,因为这样会导致领域模型变成贫血模型。

应该把不适合放在领域模型(聚合、值对象)中的方法做成领域服务(比如一些可以作为静态方法的方法),比如需要使用到资源库(需要查询)等复杂的业务逻辑。

步骤

  1. 我们要明确业务领域。先从宏观上简单的划分子域,当然领域的划分会随着讨论趋于完善与稳定。划分子域同时要考虑它依赖什么子域,将当前阶段能考虑到的都划分出去。我们可以通过领域专家描述业务场景并记录,在划分领域、建模领域模型时非常有效。

  2. 开始进行建模。对于每个子域,领域专家谈论具体业务场景,开发人员进行抽象建模,此时的建模是领域模型,并把期间谈及,生成为通用语言,列到术语表(glossary of terms)中,并以后都使用该语言进行谈论,后面引入的新术语也要及时更新通用语言。领域模型必须也要使得领域专家理解,始终让双方在一个频道上。(战略设计,strategic design)

  3. 诞生的领域模型、通用语言需要使用共享文档等工具进行维护。

  4. 开发人员开始划分限界上下文,创建上下文映射图

  5. 对于每个限界上下文,根据领域模型具体建模实体、值对象、聚合。建模过程中,可能也要反馈完善到领域模型,然后需要领域专家一同讨论修改的是否正确。(战术设计,tactical design)

  6. 根据限界上下文,划分微服务(把握不定的尽量从粗粒度划分,后续容易转为细粒度),设计微服务架构,并创建项目解决方案,开始按照设计进行上下文框体搭建。梳理开发规范。

  7. 项目开发中遇到的变更应及时反馈到通用语言、领域模型文档。