Skip to content
On this page

第 1 章 初步了解领域驱动设计

1.1 整体理解领域驱动设计

1.1.1 领域驱动设计的发展史

领域驱动设计(Domain-Driven Design,简称 DDD)是一种软件开发方法论,其强调在软件设计中以业务为核心,通过对业务的深入理解,将业务知识建模为领域模型,最终由领域模型解决复杂业务场景下的软件开发问题。

领域驱动设计最早由埃里克·埃文斯(Eric Evans)在他的著作《领域驱动设计》中提出。这本书出版于 2003 年,是领域驱动设计方法论的代表作之一。

在书中,Eric Evans 提出了一系列的概念和方法,包括领域模型、限界上下文、聚合、实体、值对象、领域服务等。这些概念和方法都是为了帮助开发人员更好地理解业务需求,并将其转化为高质量、可维护的软件代码。

在之后的岁月里,DDD 受到了越来越多开发人员的关注和认可,DDD 的理论也在不断的实践中被完善和发展。

随着互联网和移动互联网的快速发展,业务领域的复杂性不断增加,服务端架构也步入了微服务时代。在微服务的背景下,更多的开发者开始意识到领域驱动设计的价值,微服务的成功也证明了 DDD 在解决复杂软件系统设计问题方面的有效性。

由于领域驱动设计得到了越来越多的关注和认可,许多企业将领域驱动设计视作高级开发人员的必备技能,许多企业的高级软件开发工程师、架构师、技术总监等岗位在招聘时明确要求应聘者掌握领域驱动设计。

1-1

1.1.2 领域驱动设计的基本理解

1.1.2.1 领域模型是核心

领域驱动设计的核心问题在于如何构建一个能够反映业务的领域模型。通过建立领域模型,我们可以更好地理解业务需求,更准确地描述业务流程,更清晰地定义业务规则。

领域驱动设计注重业务专家和开发人员之间的沟通与合作,强调从业务角度出发进行软件设计。这种思维方式有助于提高软件开发人员对业务的理解和洞察力,进而提高软件开发的质量和效率。在实际工作中,业务专家可以是产品负责人/产品经理、运营团队、用户等,只要是对交付的软件有利益相关的相关方,都可以被视为领域专家。

领域驱动设计提倡将业务逻辑放到领域模型中。这意味着开发团队需要将业务逻辑从应用程序中抽象出来,并封装到领域模型中。这样做可以使得业务逻辑更加清晰、易于理解和维护。

为了得到有效的领域模型,我们需要进行领域建模。本书介绍了一种被称为事件风暴法的领域建模方法。通过事件风暴法,团队成员可以共同参与到领域建模过程中,通过讨论和协作来获取对业务的深入理解,并将这些理解转化为领域模型的具体实现。

1.1.2.2 战术设计是基础

战术设计关注的是领域模型的实现细节。

为了将领域建模的结果翻译成代码,DDD 提出了一些常用的模式,如实体(Entity)、值对象(Value Object)、聚合(Aggregate)、领域服务(Domain Service)、工厂(Factory)、仓储(Repository)等。这些模式都是为了更好地表达业务领域中的概念和关系,使得领域模型更加准确和完整。

可以说,逃避战术设计的 DDD 实践,其实都没有真正实践 DDD,因为领域模型根本就没有被真正实现。

1.1.2.3 战略设计是方向

战略设计关注的是领域模型的整体协作。战略设计的目标是制定一个清晰的业务规划,为后续的战术设计提供指导。 为了实现大型复杂的业务系统,领域驱动设计提出了战略设计的概念。战略设计的核心还是理解业务知识,在其基础上探讨如何实现复杂的业务。在 Eric 的书中,战略设计有三大主题:上下文、精炼和大型结构。

  • 上下文

上下文指的是业务之间会存在边界,某些领域知识可能只在某个特定业务边界内部发挥作用,其他的业务可能对其不感兴趣。比如说用户个人信息管理这个业务方,可能不会关心某些计费规则。

上下文这个大主题,包括限界上下文和上下文映射两个核心的议题。限界上下文用于定义领域模型的边界,上下文映射用于定义位于不同限界上下文的模型的关系和协作方式。

常见的上下文映射关系有共享内核(Shared Kernel)、客户/供应商(Customer/Supplier)、跟随者(Conformist)、各行其道(Separate Way)、开放主机服务(Open Host Service)、防腐层(Anti-Corruption Layer)、发布语言(Published Language)等。在微服务时代,我们需要重点关注开放主机服务、防腐层和发布语言这几种映射关系。

  • 精炼

当我们得到描述整体业务的领域模型之后,此时的模型里面已经定义好了限界上下文和上下文映射,我们需要进行进一步思考:寻找整体模型中最有价值和最特殊的部分。因为这些核心的模型是主要商业价值的提供者,最需要投入更多的资源对其进行开发,其余起到支撑作用的组件,虽然也是不可或缺的,但是并不需要投入同等的资源。

核心的模型所在的相关限界上下文构成了核心子域(Core Subdomain),其余的限界上下文则构成了非核心子域。有时候我们会将非核心子域区分为支撑子域(Supporting Subdomain)和通用子域(Generic Subdomain):支撑子域一般指不直接提供核心商业价值的子域,但是核心子域需要其支持才能运行,并且业界内没有成熟通用的解决方案;通用子域也不会直接提供核心商业价值,核心子域也需要其支持才能运行,但是业界已经存在通用的解决方案,例如开源产品等。

核心子域的识别主要有两个途径:直接识别和精炼模型。

直接识别是指还没有开始领域建模的时候,我们就已经可以初步确定一些核心的业务。例如直播带货平台,即使还没有进行领域建模,我们依旧可以清晰地明确直播就是这个平台的核心业务,因为没有直播的电商平台就只是个普通的电商平台。

精炼模型则是得到领域模型之后,进行进一步的分析,找出其提供核心商业价值的部分,划分成为核心子域。

我们不必过分苛求完美的上下文和子域,因为随着业务的发展,这些是会逐步调整的,产出一个符合当前业务阶段的领域模型即可。

  • 大型结构

大型结构是针对领域模型而言的,主要是为了解决大型系统中领域层代码的组织问题。

大型系统业务规则繁杂、代码庞大,领域建模后可能存在数不清的实体和值对象,如果随意放置,很容易造成领域模型代码结构混乱、难以维护。 战略设计的大型结构从我们可以根据代码的职责对其进行分层,并且形成一定的代码规范,使大型的结构可以持续演进。

要提醒读者注意的是,本书第 2 章讲到了应用架构,但是第 2 章的应用架构是针对项目整体的,其包括领域层、基础设施层、应用服务层和用户接口层,读者要将其与这里的“大型结构”区分开,这里的“大型结构”是战略设计,是针对领域层讲的。

1.1.3 领域驱动设计的意义

对于实践领域驱动设计的意义,作者认为我们可以从以下几个方面理解。

  • 更深入的业务理解和更流畅的团队沟通

在传统的软件开发过程中,往往存在开发人员和业务专家之间的沟通障碍。业务专家往往难以准确表达自己的需求,而开发人员也很难理解业务专家的术语和概念。

DDD 通过引入统一的语言,促进团队与业务专家的沟通与协作,可以帮助开发人员更好地理解业务需求,从而减少沟通障碍,提高开发效率。

DDD 强调将业务领域作为软件开发的核心,通过捕捉业务需求和规则,建立领域模型,帮助团队深入理解业务领域。领域模型作为通用语言的一部分,可以促进团队与业务专家的沟通与协作,使开发人员更容易理解和实现业务需求。

  • 更良好的系统架构

DDD 鼓励将领域模型作为系统的核心,将业务逻辑尽可能地封装在领域对象中。这种明确的边界和模块化的架构能够帮助团队更好地组织和管理系统的各个部分,提高系统的可扩展性和灵活性。

领域驱动设计是微服务的灵魂,这是业界目前已经形成的共识。一方面,通过战略设计使业务的边界更清晰,可以用于指导微服务的划分;另一方面,核心子域的概念可以使核心业务得到更多的关注。

对于不同上下文之间的集成,领域驱动设计中提供了非常多的上下文映射方式,可以指导我们使用合理的方式处理不同服务之间的交互。

  • 更优秀的软件质量

通过将业务领域作为软件开发的核心,DDD 可以帮助开发人员更好地组织和管理代码。DDD 使用领域模型来描述业务逻辑,使得软件系统更贴近真实世界的业务过程。通过将复杂的业务逻辑分解为小而清晰的领域对象,通过将业务逻辑封装到领域对象中,有助于减少代码的复杂性,可以使代码更易于理解和维护,提高了系统的可测试性,从而提高软件的质量。

此外,DDD 还提供了一系列设计模式和技术,如实体、值对象、聚合、仓储、领域事件等,可以帮助开发人员处理复杂的业务场景,提高系统的可扩展性和灵活性。

  • 更从容地应对需求变化

在传统的软件开发过程中,需求变化往往会导致代码的大规模修改,甚至需要重新设计整个系统。

在 DDD 中,代码和业务逻辑之间存在较强的映射关系,因此可以更加灵活地应对需求变化。通过使用聚合、领域事件、防腐层等技术,可以将系统的不同部分解耦,并使其独立地响应变化。在需求变化时,只需要修改受影响的部分,而不会对整个系统造成影响,使开发者更从容地应对需求变化。

  • 更高效的开发效率

DDD 通过将复杂业务逻辑进行分解和抽象,使开发人员能够更专注于业务需求的实现,避免了对细节和技术的过度关注。这种聚焦于业务的开发方式能够提高开发效率,减少开发的返工和调整。

1.1.4 领域驱动设计的困境

自领域驱动设计提出以来,领域驱动设计便一直备受业界关注,长久不衰。随着微服务时代的到来,领域驱动设计的火爆程度更是无出其右。

越来越多的公司开始关注 DDD 并将其应用于实际项目中,这些公司涵盖了各个行业,包括金融、保险、零售、医疗等。许多公司在招聘时,掌握领域驱动设计往往也会成为一个优先选项,对应岗位往往薪酬丰厚。

然而,与业界的火爆程度相比,领域驱动设计的落地现状则稍显尴尬,主要体现在以下几个方面。

  • 没有业界认可的开发标准

DDD 的理念和方法非常抽象和灵活,每个团队对领域驱动设计的理解不尽一样。这导致了缺乏统一的标准和规范,使得开发者很难在实际项目中应用 DDD。此外,由于缺乏标准,也很难评估和比较不同团队和个人的 DDD 实践水平,使得 DDD 的普及和推广受到了一定的限制。

有的团队重战略轻战术,认为只要画几个漂亮的限界上下文、子域划分图,就完成了领域驱动设计的落地,赶紧高兴地做漂亮的 PPT 对外分享“成功经验”。

有的团队重战术轻战略,他们一直在纠结实体、值对象、Repository 怎么实现,这个类放哪里,那个方法该怎么实现,忽视了战略设计,始终困在战术设计的泥潭无法走出来。领域驱动设计的战略设计提供了落地的整体方向,战术设计提供了实现细节,这两条腿缺失任意其一,都不算真正的落地。 对领域驱动设计的理解层面都千差万别了,更别谈领域驱动设计的实现层面了。领域驱动设计在实际编码的层面更是缺乏开发规范和编码标准,所以业界有不少声音质疑领域驱动设计是否可以落地。

本书基于作者实践领域驱动设计的经验,提供了不少代码层面的案例,也提供了两个综合实战案例(视频直播服务和 AIGC 应用),本书并不是要提供一个编码层面的开发标准,而是希望为读者拓宽思路,坚定读者“领域驱动设计可以落地”的信念。当然,作者也非常期待领域驱动设计在未来发展出一套开发标准,相信届时领域驱动设计将会展现出更强大的活力。

  • 没有统一的技术框架

虽然 DDD 强调将业务领域的概念直接映射到软件系统中,但具体如何实现这种映射并没有明确的指导和建议。这使得开发者需要在实践中自行选择和设计相应的技术框架来支持 DDD 的实施。然而,由于缺乏统一的技术框架,开发者往往需要花费大量时间和精力来研究和设计适合自己项目的框架,增加了项目的复杂性和风险。

许多技术框架号称实现了领域驱动设计,然而这些框架实在是太难上手了。本来领域驱动设计的学习难度就够高了,这些技术框架又增加了学习成本,导致初学者望而却步。

领域驱动设计不是一种技术架构,其实现与技术无关,任何的面向对象语言都可以用来实现领域驱动设计。这些技术框架只是其开发者自己的实践总结,未必适合所有团队、所有业务。

本书不会讲解任何一种 DDD 技术框架,本书所有的实践案例均采用业界事实标准上的技术组件,例如 SpringBoot。本书希望让读者意识到,哪怕我们不使用任何一种 DDD 框架,我们也可以完整实现领域驱动设计。

  • 缺乏成功案例可供参考

虽然领域驱动设计很火爆,但是真正可供参考的完整案例却少之又少,对于初学者来说,这无疑增加了学习难度。

DDD 的质疑者认为 DDD 无法落地的理由之一,便是领域驱动设计鲜有开源的成功案例。他们提出这样的质疑是有依据的,作者曾经试图在 GitHub 上寻找开源的 DDD 成功案例代码,结果发现大部分的代码都是示例案例阶段的,很少有可以应用于生产的代码。

本书结合当下最火爆的直播带货和 AIGC 场景,提供了两个可运行且具有实际应用价值的案例,并将其源码开放给所有读者,希望能为读者带来一些启发。

读者可以到本书官方网站获取案例源代码,详细地址见本书“1.4 本书的使用方式”。

1.1.5 领域驱动设计的经典著作

在学习 DDD 的过程中,阅读经典著作是非常好的方式。在此向读者推荐几本领域驱动设计的经典著作,这些著作曾经给了本书作者非常大的启发,它们分别是《领域驱动设计 软件核心复杂性应对之道》、《实现领域驱动设计》、《微服务架构设计模式》。

《领域驱动设计——软件核心复杂性应对之道》,作者 Eric Evans,该书是领域驱动设计开山之作,这本书正式提出了领域驱动设计的概念,通过阅读本书读者可以了解原汁原味的领域驱动设计理论。

《实现领域驱动设计》,作者 Vaughn Vernon,该书中介绍了 Vaughn Vernon 对如何实现领域驱动设计的理解,对读者可以起到一定的参考作用。

《微服务架构设计模式》,作者 Chris Richardson,该书可以说是领域驱动设计在微服务时代的实战。该书在讲解微服务时,结合了非常多的领域驱动设计知识,可以帮助读者将领域驱动设计与当前的微服务时代紧密联系起来。

1.2 如何学习领域驱动设计

在学习领域驱动设计前,我们要对其学习难点有清晰的认识。针对其学习难点,本书提供了一条行之有效地学习路线,作者曾将该学习路线分享给不少朋友,帮助这些朋友学习并掌握了 DDD。

1.2.1 学习难点

  • 难点一、概念和术语繁多、知识零散,难以建立知识体系

DDD 涉及许多新的概念和术语,如实体、值对象、聚合根、领域服务、工厂、仓储、领域事件等。这些概念和术语不仅需要记忆,还需要深入理解其含义和作用。

此外,DDD 还涉及到一些特定的设计模式和架构,从战略设计到战术设计、从经典四层架构到端口适配器架构等,知识的跨度比较大,初学者不清楚知识点之间的先后顺序,很难快速建立知识体系,导致无法整体上掌握 DDD。

  • 难度二、案例有限

很难找到成功的开源项目案例,前文介绍过这个情况。

  • 难度三、难以将 DDD 结合实际进行应用

实际开发中,我们很难孤立地仅仅使用 DDD 完成工作,我们通常需要结合有许多的开发方法,例如设计模式、敏捷开发、测试驱动开发等,目前很少有这方面成功经验。

1.2.2 学习路线

鉴于 DDD 学习的难点,本书提供了作者本人学习 DDD 的路线,该学习路线分为以下几个阶段。

1-2

第一阶段,解决应用架构的问题。本书会带领读者从经典三层贫血架构出发,推导可以应用于 DDD 的应用架构。之所以要自己推导,是希望读者掌握这个思维的过程,加深印象以便在实际中灵活应用。我们接下来学习的领域驱动设计的知识,不管是战术设计还是战略设计,都可以按图索骥地将其在这个应用架构中进行实现。在这个应用架构中,我们要了解领域对象的生命周期,只有掌握了其生命周期,才能对领域模型在应用架构中的类型流转了如指掌。

第二阶段,掌握 DDD 的战术设计。我们将对战术设计相关的核心概念进行学习:实体、值对象、聚合/聚合根、领域服务、Repository、Factory 等,掌握这些,基本上就可以开发一些简单的应用了。

第三阶段,掌握使用 DDD 实现复杂业务逻辑的思路。我们要学习如何实现复杂业务逻辑,主要包括使用 GoF 设计模式、防腐层、规约模式等实现复杂业务逻辑。我们学习这些模式的目的,是将其应用在 DDD 开发中。可以说,通过这个阶段的学习,我们已经可以解决大部分的业务问题了。

第四阶段,掌握领域事件和事件溯源。在这个阶段,我们会首先学习幂等设计,幂等设计可以确保我们的服务支持安全的重试,避免重复请求影响业务的正确性。在幂等的前提下,我们学习如何建模领域事件并安全可靠地发布、订阅领域事件,以及如何实现 CQRS 和事件溯源。作者认为业界部分开发者在 CQRS 的理解上是存在误区的,希望读者注意鉴别。本书针对事件溯源提供了 3 个可以运行的案例代码,这些案例代码没有依赖任何 DDD 框架,读者可以非常轻松掌握事件溯源的原理和实现方案,案例的获取方式见本书“1.4 本书的使用方式”相关内容。

第五阶段,我们需要掌握 DDD 下的一致性实现方案。这里的一致性包括聚合内的一致性以及跨聚合的一致性。其实在 Repository 中我们会探讨一些一致性的问题,但是一致性太重要,直接关系到我们业务操作结果的正确性,所以我们需要单独去探讨学习。

第六阶段,我们将学习 DDD 的战略设计以及领域建模。我们先学习战略设计,理解上下文、上下文映射以及子域的概念,之后我们学习事件风暴法进行领域建模。当我们学习领域建模的时候,意味着我们领域驱动设计的相关知识已经掌握到一定程度了,我们只有理解了 DDD 战术和战略设计,才能清晰地了解领域建模究竟需要关注哪些方面。

第七阶段,我们将学习一些扩展实践。DDD 不是单独的理论体系,我们要探索如何将 DDD 融入其他开发理论,包括如何提高研发效能、测试驱动开发、敏捷开发、C4 架构模型。研发效能的章节学习,是为了帮助我们提高开发效率,改变业界对 DDD 根深蒂固的“笨重、慢”的糟糕的印象,其中涉及到脚手架、代码生成器、静态代码扫描、低代码等方面。敏捷开发的章节,则是帮助我们掌握敏捷开发理论,探索将 DDD 与敏捷开发结合起来使用。C4 架构模型这一章节,则是帮助读者掌握架构可视化的技能,合理美观地表达我们的架构设计思路。

第八阶段,我们将进入实战案例学习。直播带货和 AIGC(尤其是 ChatGPT)是目前业界最火爆的两个概念,本书针对这两个场景分别提供了案例。这两个案例完全采用本书的知识点进行实现,代码完全开源并且可以正常运行。通过实战案例的学习,我们把繁杂的知识融会贯通,使 DDD 真正成为我们一项具有竞争力的技能。

1.3 领域驱动设计常见误区

业界对于领域驱动设计存在不少的理解误区,本节针对一些常见的误区进行探讨。

1.3.1 领域驱动设计的适用范围

一直以来,业界有这么一个论调:简单的系统不适合 DDD,复杂的系统才适合 DDD。事实真的如此吗?有什么量化的标准去评价系统是否适合实施 DDD 吗?

如果一套实践理论,在简单的场景表现不好,但是在复杂的场景实践比较很好,那么这套理论真的在复杂场景能表现好吗?我认为是不能的,复杂系统拆分之后也是由很多简单的子系统构成的,而且 DDD 很多时候也是微服务拆分的神兵利器。

之所以有“简单的系统不适合 DDD,复杂的系统才适合 DDD”的这么一个论调,完全不是因为 DDD 在简单的系统没有落地的可行性,更多的其他方面的考量,例如:学习成本、研发效率、风险控制等。

  • 学习成本

DDD 的学习门槛比较高,初学者需要理解大量的概念,假如大部分团队成员缺乏 DDD 实战经验,往往则需要组织团队成员进行培训。培训一方面需要花费额外的成本,另一方面仓促的培训可能很难取得很好的效果。

  • 研发效率

DDD 的聚合根有一个原则:一次事务操作只更新一个聚合根,跨聚合根的操作采用最终一致性。相比事务脚本的方式一次性操作多张数据表,DDD 为了保证跨聚合操作的最终一致性,需要投入大量的研发资源以解决技术细节问题,很有可能给项目正常交付造成压力。

另外,目前 DDD 在实践过程中缺乏提效工具,导致给人一种笨重的感觉。

  • 风险控制

学习成本和研发效率这两个方面本身也给我们的项目实施带来潜在的风险。

每个团队对 DDD 的理解差异很大,如果团队缺乏 DDD 成功的经验,还有可能存在技术可行性上的风险,例如项目实施过程中发现某些架构层面的理解不到位,导致不得不进行返工。

另外,我们构建一些简单的系统时,往往是处在业务落地的初级阶段,团队整体缺乏领域专业知识,给领域驱动设计带来实践障碍。事实上,在业务落地的初期,我非常推荐采用事务脚本的方式进行面向数据的编程。事务脚本给予我们多表操作的能力,使得我们开发非常快捷(虽然也很显得粗暴),我们尽快把开发成果交付了,能帮助企业快速进行商业模式试错。

《实现领域驱动》这本书的第 1 章介绍了一种 DDD 计分卡的方式,得分在 7 分以上,就推荐考虑实施 DDD。这种打分的方式有一定的依据,但是本书作者会通过更简单快捷的方式去判断我们是否适合实施 DDD,作者的判断方式是这样的:如果目标系统无法在一个数据库事务里进行跨聚合更新,那么就直接选择 DDD。

当我们分库分表、某个业务服务切分到外部团队单独数据库存储时,我们没有办法保证在一个数据库事务里完成跨聚合的变更,既然无法满足一个数据库事务中进行操作多张数据表,那么就应该果断选择 DDD。 DDD 要求一次数据库事务只能更新一个聚合,聚合之间要通过最终一致性保持一致,非常适合这种场景。如果某个项目设计之初一开始就需要分库分表,那我们一开始就可以实施 DDD。

1.3.2 贫血模型与充血模型的选择

DDD 落地永远绕不开贫血模型和充血模型的争议。关于贫血模型和充血模型的选择,我们将在“2.1 贫血模型和充血模型”中进行详细探讨。 贫血模型最终会导致 Service 层方法过度膨胀。完整的 DDD 落地是要求充血模型的,如果一个 DDD 落地选择了贫血模型,那么对 DDD 的落地是不完整的。

作者旗帜鲜明地推荐使用充血模型进行 DDD 落地。

1.3.3 领域驱动设计落地的认知差异

业界对于领域驱动设计落地地认知,主要有以下两种观点。

1.3.3.1 DDD 只有战略设计层面的落地

持这个观点的 DDD 实践者认为,DDD 只能进行战略层面的落地,战术层面的落地是行不通的。

作者认为这部分实践者主要是被 DDD 战术落地难的困境吓倒,他们找不到正确落地的完整实践,所以对 DDD 战术设计持悲观的态度。

DDD 战略设计层面的实践无疑是具有极大价值的,起码在大方向上完成了限界上下文划分、子域识别。但是,忽略战术设计会丢弃了 DDD 战术设计相关的良好实践,所以没有办法产出高质量的代码,我们经常看到很多号称落地 DDD 的项目,到最后又开倒车改成分层贫血架构,就是因为缺少了 DDD 战术设计方面的努力。

我们也要理解,此类观点能在业界盛行,主要还是由于 DDD 落地没有很好的案例、规范、配套研发提效工具。这也是笔者目前正在致力的方向:写教程、给案例、出规范、建生态、推工具,期待未来更多的有志者加入到这个队伍中。

1.3.3.2 DDD 只关注战术落地

持这个观点的,大部分是一些刚接触 DDD 的实践者。由于他们对 DDD 理解的深度不够,所以习惯性地从技术角度去理解 DDD,这导致他们经常纠结要选什么架构、这个类怎么实现、那个方法放哪里合适等细节问题。由于他们过分关注细节,他们忽略了从整体上把握 DDD,即不了解战略设计,也不理解战术设计,在实践的过程中束手束脚,举步维艰,最后往往会放弃拥抱 DDD。

本书“1.2.2 学习路线”章节相关内容,正是为这些对 DDD 缺乏全局理解,缺少学习方向的初学者提供的。

1.3.4 领域驱动设计的技术选型

领域驱动设计(DDD)是一种与技术无关的开发方法,不管用什么开发语言、用什么技术框架,都不影响 DDD 的落地。

在技术选型上,我们使用目前业界通用的开源组件即可完成 DDD 的落地,本书案例的技术选型如下。

选型说明
后端框架Spring BootJava 业界目前事实上的开发框架标准
对象关系映射框架MyBatis、Spring Data JDBCMyBatis 用于解决复杂查询
缓存Redis开源 NoSQL 数据库
数据库MySQL开源关系型数据库
消息处理Kafka开源的消息队列
前端框架Vue用于开发前端页面

本书的一个特点就是使用常见、通用的开源技术组件去实现 DDD,不把 DDD 落地与冷门偏门、学习成本高的组件进行捆绑销售。

在应用架构上,用来实现 DDD 的应用架构有很多,例如经典四层架构、六边形架构等,很多初学者光是选择架构就挑花了眼。我们将会在第 2 章中专门讲 DDD 的应用架构,带领读者完整地推导我们自己的应用架构,本书所有随书案例都是基于这个应用架构开发的。

DDD 学习成本高的一个原因是 DDD 业界存在很多假大师。他们并不是 DDD 真正的实践者,他们只是 DDD 的碰瓷者,这些人或者挖空心思提出了许多花里胡哨、似是而非的概念,或者搞了很多所谓的架构和框架,将自己包装成为 DDD 专家,打着落地 DDD 的名号忽悠了不少初学者,赚足了名声。这些碰瓷者共同的特点是几乎都提出了他们自己的 DDD 概念,看似推动 DDD 的理论完善,但仔细推敲又实则什么也没有说到,少有落地的东西。初学者花费不少时间和精力学习之后才发现自己被欺骗了,进而对 DDD 持消极态度。

对于这种热衷炒概念的碰瓷者,作者是深恶痛绝的,他们这样做无疑使得 DDD 臭名昭著,容易使业界认为 DDD 只有假大空的概念而没有落地的可行性。一个好的理论方法应该是把事情变得简单,而不是增加复杂度,把简单地问题复杂化。作者也向这些碰瓷者学习,提出了一个新的 DDD 概念——DDD 骗子(DDD Con Man,简称 DDDCM),专门用来指代这些 DDD 碰瓷者,这是本书唯一提出的新的概念。

1.3.5 DDD 与面向对象编程

作者经常被提问关于 DDD 与面向对象编程的关系问题,例如:“既然已经有了面向对象编程,为什么还要去搞领域驱动设计?”、“领域驱动设计把面向对象编程颠覆了吗?”、“面向对象编程的 SOLID 原则在领域驱动设计下还适应吗?”等问题。

既然已经有了面向对象编程,为什么还要去搞领域驱动设计呢?首先,面向对象编程强调的是对象的行为和状态,而领域驱动设计则更加注重业务领域的模型和语言。它通过将业务领域中的概念、规则和流程映射到软件设计中,使得软件系统更加贴近业务需求,更易于理解和维护。其次,领域驱动设计提供了一些实践经验和工具,帮助开发团队更好地理解和应对复杂业务场景。例如,DDD 中的聚合、实体、值对象等概念能够帮助开发者更好地组织代码结构,降低系统的复杂度。

领域驱动设计把面向对象编程颠覆了吗?并不是。领域驱动设计并不是要取代面向对象编程,而是要在面向对象编程的基础上进一步完善和优化软件设计。事实上,DDD 中的许多概念和原则都与面向对象编程密不可分。例如,聚合、实体、值对象等概念都是基于面向对象编程中的类和对象而来。另外,DDD 中的 SOLID 原则也是面向对象编程中常用的原则之一。

如何理解领域驱动设计和面向对象编程的关系呢,我们举一个汽车厂的例子进行说明。有一个可以同时生产卡车和公交车的汽车厂,起初的时候他们在一个车间里同时生产卡车和公交车。这个把零件装成整车的过程就是我们的面向对象编程。后来,我们发现同时在一个车间生产两种车,很容易造成人员安排混乱、设备升级困难(因为要兼顾两种类型的车)等问题,于是我们分别梳理这两种车的生产过程,将其安排到不同的车间中进行生产,各自只生产一种类型的车,这个过程就是领域驱动设计。拆分后的车间生产卡车或者公交车时,依旧是把零件装成整车,也就是说还是面向对象编程。所以说,面向对象编程是领域驱动设计的基础,领域驱动设计是对面向对象编程的拓展和完善。

面向对象编程的 SOLID 原则在领域驱动设计下还适应吗?答案是肯定的。SOLID 原则包括单一职责原则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则。这些原则都是为了提高代码的可读性、可维护性、可扩展性和可重用性。而在 DDD 中,这些原则同样适用。例如,在 DDD 中,聚合就是一个高度内聚、低耦合的单元,它与单一职责原则是一致的;而在实现聚合时,我们也需要遵循开闭原则,以便让聚合能够适应变化。

1.3.6 学了DDD就一定能合理划分业务边界

学了DDD并不一定保证我们一定能合理地划分业务边界。DDD只是一种设计方法,它并不能自动地为我们划分业务边界,也没有提供定量的实践标准。因此,不要寄希望于学习了DDD之后,就一定能完美地划分业务边界。

划分业务边界需要对业务领域有深入的了解,需要与业务专家紧密合作,需要对各种业务场景有全面的认知。只有在与业务专家深入交流、对业务进行全面分析的基础上,才能够合理地划分业务边界。

实际上,"合理的业务边界"本身就是伪命题,没有放之四海而皆准、没有一成不变的业务边界,我们只能寻求当下合理的业务边界。

至于该如何使用DDD去划分业务边界,以下是作者的一些建议。

  • 了解业务领域

我们需要对业务领域有深入的了解,这包括了解业务的核心概念、业务流程、业务规则等。只有对业务领域有全面的认识,才有可能合理地划分业务边界。

在了解业务领域的过程中,我们需要与领域专家紧密合作。业务专家是对业务最了解的人,他们能够提供关于业务流程、规则等方面的详细信息帮助,帮助我们更好地理解业务需求。

  • 领域建模+战略设计

划分业务边界的过程,也是我们将一个大型复杂的业务领域划分为多个小而简单的子领域的过程。

通过领域建模,我们可以得到具体的聚合,进而得到明确的限界上下文;通过战略设计,从这些限界上下文中精炼得到子域。在划分子领域时,我们需要考虑每个子领域所包含的核心概念、业务流程、规则等方面,并将其与其他子领域进行区分。明确限界上下文、子域的范围,可以帮助我们更好地划分业务边界。

  • 不断迭代优化

划分业务边界是一个不断迭代优化的过程。在实际应用中,我们可能会发现一些子领域之间的关系不太清晰,或者某些子领域需要进一步拆分。在这种情况下,我们需要及时进行调整和优化,以确保划分出来的子领域能够更好地支持业务需求。

1.4 本书的使用方式

作者已经为本书搭建了官方网站,网站中不仅提供了部分章节的在线阅读,还提供了随书所有案例的源代码。读者在学习的过程中如果遇到问题,也可以通过官方网站联系作者进行答疑。

1-3

本书官网地址如下。

text
http://ddd.feiniaojin.com/
或者
https://github.com/feiniaojin/Thinking-in-DDD

由于作者水平有限,书中错误在所难免,欢迎读者通过以上地址进行错误或者问题反馈。

本书知识星球

欢迎加入本书知识星球以获得作者答疑。

星球QR

本书读者交流微信群

欢迎加入 DDD 交流群。微信扫以下二维码添加作者微信,标注“DDD”,好友申请通过后拉您进群。

qr.jpg

版权声明

本作品代码部分

采用 Apache 2.0 协议进行许可。

遵循许可的前提下,你可以自由地对代码进行修改,再发布,可以将代码用作商业用途。但要求你:

署名:在原有代码和衍生代码中,保留原作者署名及代码来源信息。

必须提供作者的署名以及本作品的链接(http://ddd.feiniaojin.com/)

保留许可证:在原有代码和衍生代码中,保留 Apache 2.0 协议文件。

本作品文档、图片等内容部分

采用署名-非商业性使用-禁止演绎 4.0 国际 (CC BY-NC-ND 4.0 DEED)进行许可。

在遵守以下条件的前提下:

署名: 您必须给出 适当的署名 ,提供指向本许可协议的链接,同时 标明是否(对原始作品)作了修改 。您可以用任何合理的方式来署名,但是不得以任何方式暗示许可人为您或您的使用背书。

引用本作品任何内容时,必须提供作者的署名以及本作品的链接(http://ddd.feiniaojin.com/)

非商业性使用: 您不得将本作品用于 商业目的 。

在媒体、自媒体平台(包括但不限于微信公众号、头条号等)转载、二次创作、发表等行为将被视为商业应用,必须取得作者的授权。

禁止演绎: 如果您 再混合、转换、或者基于该作品创作 ,您不可以分发修改作品。

基于本作品任何内容,进行任何形式(包括但不限于文章、视频、语音、有声书等)的二次创作(包括翻译为其他语言),都必须取得作者的授权。

没有附加限制: 您不得适用法律术语或者 技术措施 从而限制其他人做许可协议允许的事情。

您可以自由地:

共享: 在任何媒介以任何形式复制、发行本作品。

只要你遵守许可协议条款,许可人就无法收回你的这些权利。