第 1 章 领域驱动设计预热
1.1 整体理解领域驱动设计
1.1.1 领域驱动设计的历史背景
领域驱动设计(Domain-Driven Design,DDD)是一种以业务为核心的软件开发方法论, 通过深入理解业务,将业务知识建模为领域模型,最终解决复杂业务场景下的软件开发问题。
领域驱动设计最早由 Eric Evans 在他的著作《领域驱动设计:软件核心复杂性应对之道》 (Domain-Driven Design: Tacking Complexity in the Heart of Software)中提出。该书是领域驱动设计方法论的代表作之一。
在《领域驱动设计》中,Eric Evans 提出了一系列的概念和方法,包括领域模型、限界上下文、聚合、实体、值对象、领域服务等。这些概念和方法旨在帮助开发人员更好地理解业务 需求,并将其转化为高质量、可维护的软件代码。
在之后的岁月里,领域驱动设计受到了业界越来越多的关注和认可,其理论也在实践中被 不断地完善和发展。
随着互联网和移动互联网的快速发展,业务领域的复杂性不断增加,服务端架构也步入了 微服务时代。在微服务的背景下,越来越多的开发者意识到领域驱动设计的价值,同时微服务 的成功也证明了领域驱动设计在解决复杂软件系统设计问题时的有效性。
许多企业将领域驱动设计视为高级开发人员的必备技能,尤其是在招聘高级软件开发工程 师、架构师、技术总监等职位时,往往明确要求应聘者掌握领域驱动设计思想。
1.1.2 领域驱动设计的基本理解
1.1.2.1 领域模型提供核心价值
领域模型是对领域知识的抽象建模,描述业务流程,定义业务规则,执行业务操作,提供 核心业务价值。 领域模型来自对业务的深入理解和抽象,这个理解和抽象业务知识的过程被称为领域建 模。为了得到有效的领域模型,业界提出了许多经过实践检验的建模理论,如事件风暴法。通 过事件风暴法,团队成员可以共同参与到领域建模过程中,分享自己的关注点以及业务理解, 并按照事件风暴法约定的建模元素将其表达出来,将这些业务理解转化为领域模型。
在领域建模的过程中,领域驱动设计提倡开发者与领域专家通力合作,在领域专家的帮助 下深入理解业务。实际上,在任何阶段都应该加强与领域专家的沟通和合作,持续优化领域模型。这里提到的领域专家可能是产品经理(或者产品负责人)、运营团队、最终用户等,只要 是对交付的软件有利益关系的相关方,都可以被视为领域专家。
领域驱动设计提倡将业务逻辑封装在领域模型中,这意味着开发者要避免将业务逻辑与基 础设施操作混杂在一起,也就意味着要尽量避免贫血模型。
1.1.2.2 战术设计提供实现细节
战术设计关注的是领域模型的实现细节。
通过领域建模得到领域模型后,需要将领域模型实现成高质量的代码。领域驱动设计提 出了一些常用的模式,如实体(Entity)、值对象(Value Object)、聚合(Aggregate)、聚合 根(Aggregate Root)、领域服务(Domain Service)、工厂(Factory)、防腐层(Anti-Corruption Layer)、仓储(Repository)等。这些模式提供了行之有效的实现方法,通过这些模式可以很 好地使用代码表达领域模型,使领域模型能准确和完整地实现为高质量的代码。
可以说,逃避战术设计的领域驱动设计实践,都没有真正实践领域驱动设计,因为领域模 型根本就没有被真正实现。
1.1.2.3 战略设计提供全局视野
战术设计关注的是领域模型的技术实现细节,而战略设计关注的是领域模型之间的整体协 作。战略设计在领域模型的基础上,首先根据业务边界提出了限界上下文的概念,然后根据限 界上下文之间的协作方式提出了上下文映射的概念,最后根据限界上下文的业务价值提出了子 域的概念。限界上下文、上下文映射和子域这些概念的提出,都是为了从全局的视角划分业务 边界,以便制定清晰的业务规划。
由于战略设计提供了全局的视角,因此战略设计是梳理大型复杂业务系统的利器。通过战略设计,可以将大型复杂的业务不断切分为相对简单的子域、上下文,达到化整为零的效果。战略设计的核心还是理解业务知识,在领域模型的基础上探讨如何实现复杂的业务。在Eric Evans 的书中,战略设计有三大主题:上下文、精炼和大型结构。
- 1)上下文
上下文指的是业务之间存在的边界,某些领域知识可能只在特定的业务边界内发挥作用, 其他业务可能对其不感兴趣。比如,用户个人信息管理的业务方可能不会关心某些计费规则。 上下文大主题包括限界上下文和上下文映射两个核心议题。限界上下文用于定义领域模型的边界,上下文映射用于定义位于不同限界上下文的模型的关系和协作方式。
常见的上下文映射关系有共享内核、客户 / 供应商、跟随者、各行其道、开放主机服务、防腐层、发布语言 等。在微服务时代,需要重点关注开放主机服务、防腐层和发布语言等映射关系。
- 2)精炼
得到描述整体业务的领域模型之后,此时的模型已经定义好了限界上下文和上下文映射, 需要进一步思考,寻找整体模型中最有价值和最特殊的部分。因为这些核心的模型是主要商业 价值的提供者,最需要投入更多的资源对其进行开发,其余起到支撑作用的组件虽然也是不可 或缺的,但是并不需要投入同等的资源。
核心的模型所在的相关限界上下文构成了核心子域(Core Subdomain),其余的限界上下 文则构成了非核心子域。有时候会将非核心子域区分为支撑子域(Supporting Subdomain)和 通用子域(Generic Subdomain):支撑子域一般指不直接提供核心商业价值的子域,但是核心 子域需要其支持才能运行,并且业界没有成熟、通用的解决方案;通用子域也不会直接提供核 心商业价值,核心子域也需要其支持才能运行,但是业界已经存在通用的解决方案,可以通过 采购或者引入开源产品的方式获得。
核心子域的识别主要有两个途径:规划和精炼。
规划是指还没有开始进行领域建模时,就已经可以初步确定一些核心的业务。例如直播带 货平台,即使还没有进行领域建模,依旧可以清晰地明确直播就是这个平台的核心业务,因为 当前没有直播的电商平台就只是一个普通的电商平台。规划是一种自上而下的过程。
精炼则是在得到领域模型之后进一步分析,找出其提供核心商业价值的部分,划分成为核 心子域。精炼是一种自下而上的过程。 不必苛求完美的上下文和子域,因为随着业务的发展,这些会逐步调整,能够产出一个符 合当前业务阶段的领域模型即可。
- 3)大型结构
大型结构是针对领域模型而言的,主要是为了解决大型系统中领域层代码的组织问题。 大型系统的业务规则繁杂,代码庞大,领域建模后可能存在数不清的实体和值对象。如果随意放置,很容易造成领域模型代码结构混乱、难以维护。 战略设计的大型结构使我们能够根据代码的职责对其进行分层,并且形成一定的代码规范,使大型结构可以持续演进。
本书第 2 章讲到了应用架构,请注意将这里的大型结构与第 2 章的应用架构进行区分。第 2 章的应用架构是针对项目整体的,包括领域层、基础设施层、应用层和用户接口层。这里的 “大型结构”是战略设计,是针对领域层讲的,研究如何组织领域层的领域模型。
1.1.3 领域驱动设计的意义
笔者认为可以从以下几个方面理解实践领域驱动设计的意义。
- 更流畅的团队沟通
领域驱动设计提倡在团队中建设通用语言,团队成员(包括领域专家)之间都采用通用语 言进行沟通和交流。通用语言是团队内部达成的共识,采用通用语言进行沟通不会引起歧义, 减少了沟通障碍,可以帮助开发人员更好地理解业务需求。
- 更深入的业务理解
领域驱动设计提倡开发者加强与领域专家的沟通和合作,在沟通的过程中不断完善通用语 言,减少业务理解难度。
领域驱动设计通过事件风暴法等领域建模方法,将业务知识建模成领域模型,领域建模的 过程也就是理解业务的过程。在领域模型中完整地表达了业务规则和业务过程,业务相关的知 识都被封装到领域模型中,得到了很好的抽象和维护,也更方便开发者理解业务。
- 更良好的系统架构
领域驱动设计将领域模型作为系统的核心,将业务逻辑尽可能地封装在领域对象中。除了 领域模型,系统其余的部分不包括业务逻辑,可以根据需要进行技术选型。这意味着可以选择 性能更高的中间件、选择扩展性更好的应用架构、选择可用性更好的部署方案,完成高性能、 高流量、高可用的系统架构设计。
领域驱动设计是微服务的灵魂,这是业界目前已经形成的共识。一方面,通过战略设计使 业务的边界更清晰,可以用于指导微服务的划分;另一方面,核心子域的概念可使核心业务得 到更多的关注,从而可以用于指导资源的分配。
对于不同上下文之间的集成,领域驱动设计提供了非常多的上下文映射方式,例如开放主 机服务、防腐层等,可以指导不同服务之间的交互实现。
- 更优秀的代码质量
领域驱动设计将业务逻辑封装到领域模型中,可以非常高效和方便地对业务代码进行测 试,结合测试驱动开发(TDD)、静态代码分析等,可以很好地提高代码的质量。
此外,领域驱动设计还提供了一系列的模式,如实体、值对象、聚合、仓储、领域事件 等,可以帮助开发人员处理复杂的业务场景,提高系统的可扩展性和灵活性。
- 更从容地应对需求变更
在传统的软件开发过程中,需求变更往往意味着代码的大规模修改,甚至需要重新设计整 个系统,给软件的顺利交付带来风险。
在领域驱动设计中,业务代码和基础设施操作是分离的。代码是领域模型的实现,而领域 模型和业务逻辑之间存在较强的映射关系。在变更业务需求时,由于开发者只需要关注领域模 型的调整,因此可以将业务逻辑变更的影响范围控制在领域模型内。而对于领域模型以外的技 术实现细节,即使技术实现方案产生了变更,也不会对领域模型造成影响。
由此可见,领域驱动设计可以使开发者更从容地应对需求变更。
- 更高效的开发效率
领域驱动设计通过将复杂业务逻辑和技术性的基础设施操作分离,使开发人员能够更专注于业务需求的实现,避免了对基础设施操作的过度关注。这种聚焦于业务的开发方式能够提高开发效率,减少返工和变更频次。
1.1.4 领域驱动设计的困境
领域驱动设计理论自从被提出以来就备受业界关注,并持续受到关注。随着微服务时代的 到来,领域驱动设计的重要性更加凸显。越来越多的公司开始关注领域驱动设计,并将其应用 于实际项目中。
然而,与业界的火爆程度相比,领域驱动设计的落地现状则稍显尴尬,主要体现在以下几 个方面。
1.1.4.1 没有业界认可的开发标准
领域驱动设计的理念和方法非常抽象和灵活,每个团队对领域驱动设计的理解不尽不同。 这导致缺乏统一的标准和规范,使得开发者很难在实际项目中应用领域驱动设计。此外,由于 缺乏标准,也很难评估和比较不同团队和个人的领域驱动设计实践水平,从而限制了领域驱动 设计的普及和推广。
有些团队过于注重战略设计而忽视了战术设计,认为只要制作一些精美的限界上下文和子 域划分图,就能够完成领域驱动设计的落地,并急于分享这种“成功经验”。
有些团队则过于注重战术设计而忽视了战略设计。他们一直在纠结于实体、值对象、 Repository 等概念的实现方式,以及类的放置和方法的实现方式,忽视了战略设计,从而陷入 战术设计的困境无法自拔。领域驱动设计的战略设计提供了落地的全局视角,而战术设计则提 供了具体实现的局部细节,这两条腿缺失任意其一,都不算真正落地。
领域驱动设计的理解层面都千差万别了,更别谈领域驱动设计的实现层面了。只有同时考 虑这两个方面,领域驱动设计才能被真正落地。
在实现编码层面,领域驱动设计缺乏统一的开发规范和编码标准,因此业界有很多质疑声 音,质疑领域驱动设计是否可以被真正落地。
本书根据作者在领域驱动设计实践中的经验,除了提供许多代码层面的案例,还提供了 两个综合实战案例(视频直播服务和 AIGC 产品)。本书的目的不是提供编码层面的开发标准, 而是希望帮助读者拓宽思路,坚信“领域驱动设计可以被落地应用”。当然,作者也非常期待 领域驱动设计在未来能够发展出一套统一的开发标准,相信届时领域驱动设计将展现出更加强 大的活力。
1.1.4.2 没有统一的技术框架
尽管领域驱动设计强调将业务领域的概念直接映射到软件系统中,但具体如何实现这种映 射并没有明确的指导和建议。这使得开发者需要在实践中自行选择和设计相应的技术框架来支 持领域驱动设计的实施。然而,由于缺乏统一的技术框架,开发者通常需要花费大量时间和精 力来研究和设计适合自己项目的框架,这增加了项目的复杂性和风险。
许多技术框架号称实现了领域驱动设计,然而这些框架实在是太难上手了。本来领域驱动 设计的学习难度就够高了,这些技术框架又增加了学习成本,导致初学者望而却步。
领域驱动设计不是一种技术架构,其实现与技术无关,任何面向对象的语言都可以用来实现领域驱动设计。这些技术框架只是其开发者自己的实践总结,未必适合所有团队和所有 业务。
本书不会讲解任何一种领域驱动设计技术框架,本书所有的实践案例均采用业界事实标准 上的技术组件,例如 Spring Boot。本书希望让读者意识到,哪怕不使用任何一种领域驱动设计 框架,也可以完整实现领域驱动设计。
1.1.4.3 缺乏可供参考的成功案例
虽然领域驱动设计很火爆,但是真正可供参考的完整案例却少之又少,对于初学者来说, 这无疑增加了学习难度。
领域驱动设计的质疑者认为领域驱动设计无法落地的理由之一,便是领域驱动设计鲜有开 源的成功案例。他们提出这样的质疑是有依据的,笔者曾经试图在 GitHub 上寻找开源的领域驱 动设计案例代码,结果发现大部分代码都是示例案例阶段的,很少有可以应用于生产的代码。
本书结合直播带货和 AIGC 场景,提供了两个可运行且具有实际应用价值的案例,并将其 源码开放给所有读者,希望能提供一些启发。
本案例配套源代码获取地址详见前言结尾的“读者服务”。
1.2 如何学习领域驱动设计
在学习领域驱动设计前,我们要对其学习难点有清晰的认识。针对其学习难点,本书提供 了一条行之有效的学习路线。
1.2.1 学习难点
1.2.1.1 难以建立知识体系
领域驱动设计涉及许多新的概念和术语,如实体、值对象、聚合根、领域服务、工厂、仓 储、领域事件等。这些概念和术语不仅需要记忆,还需要深入理解其含义和作用。
此外,领域驱动设计还涉及一些特定的设计模式和架构,从战略设计到战术设计,从经 典四层架构到端口适配器架构等。知识的跨度比较大,初学者如果不清楚知识点之间的先后顺 序,很难快速建立知识体系,导致无法从整体上掌握领域驱动设计。
1.2.1.2 案例有限
如前文所述,很难找到成功的开源项目案例。
1.2.1.3 难以结合实际开发过程进行应用
在实际开发中,很难孤立地使用领域驱动设计完成工作,通常需要结合许多开发方法,如设计模式、敏捷开发、测试驱动开发等。
1.2.2 学习路线
本书提供了领域驱动设计的学习路线如图 1-1 所示。
图1-1 领域驱动设计的学习路线图
该学习路线分为几个阶段,分别如下:
第一阶段,解决应用架构的问题。本书带领读者从经典的三层贫血架构出发,推导可以 落地领域驱动设计的应用架构。之所以要自己推导,是希望读者掌握这个演化的过程,加深印 象,以便在实践中灵活应用。接下来关于领域驱动设计的知识,不管是战术设计还是战略设 计,都可以按图索骥地在这个应用架构中实现。要了解领域对象的生命周期,只有掌握了其生 命周期,才能对领域模型在应用架构中的类型变化了如指掌。
第二阶段,掌握领域驱动设计的战术设计。这个阶段需要学习战术设计相关的核心概念, 包括:实体、值对象、聚合 / 聚合根、领域服务、Repository、Factory 等。掌握这些战术设计 的概念后,基本上就可以开发一些简单的应用了。
第三阶段,掌握使用领域驱动设计实现复杂业务逻辑的基本思路。这个阶段要学习使用设 计模式、防腐层、规约模式等实现复杂的业务逻辑,并将其应用在领域驱动设计的开发中。通 过这个阶段的学习,读者能够使用领域驱动设计解决大部分的业务问题。
第四阶段,掌握领域事件和事件溯源。这个阶段首先需要学习幂等设计,因为它可以 确保服务支持安全的重试,避免重复请求影响业务的正确性。在幂等的前提下,掌握如何 建模领域事件并安全可靠地发布、订阅,以及如何实现命令查询责任分离(Command Query Responsibility Segregation,CQRS)和事件溯源。业界部分开发者对 CQRS 的理解是存在误区 的,读者在 CQRS 的学习过程中需要关注概念的理解。本书针对事件溯源提供了 3 个可以运行的案例代码,它们没有依赖任何领域驱动设计框架,通过阅读这些案例,读者可以非常轻松 地掌握事件溯源的原理和实现方案。案例代码的获取方式见本书前言结尾的“读者服务”。
第五阶段,需要掌握领域驱动设计下的一致性实现方案。这里的一致性包括聚合内的强一 致性以及跨聚合的最终一致性。在战术设计介绍 Repository 时,会涉及一些一致性的讨论,但 是由于一致性太重要了,直接关系到业务操作结果的正确性,所以需要单独探讨。
第六阶段,学习领域驱动设计的战略设计和领域建模。先学习战略设计,理解限界上下 文、上下文映射及子域的概念,之后学习事件风暴法进行领域建模。战略设计和领域建模之所 以被放在靠后的阶段,是因为只有充分理解了领域驱动设计的战术设计,有了一定的知识储 备,才能清晰地理解战略设计和领域建模。
第七阶段,综合实践。领域驱动设计不是孤立的理论体系,不能脱离实际研发过程。本阶 段的目标是掌握如何将领域驱动设计融入其他开发理论中,例如,研发效能、测试驱动开发、 敏捷开发、C4 模型等。研发效能章节的学习旨在提高开发效率,改变业界对领域驱动设计笨 重、慢、糟糕等刻板的印象,其中涉及的知识包括脚手架、代码生成器、静态代码扫描、低代 码等。敏捷开发章节的学习则帮助读者掌握敏捷开发理论,并将领域驱动设计融入实际的敏捷 开发中。C4 模型章节的内容有助于读者掌握架构可视化的技能,合理地表达架构设计思路。
第八阶段,案例实战。直播带货和 AIGC(尤其是 ChatGPT)是目前业界最火爆的两个概 念。本书针对这两个场景分别提供了代码案例。这两个案例完全采用本书的知识点进行实现, 代码完全开源且可以正常运行。通过实战案例阶段的学习,可以将繁杂的知识融会贯通,使领 域驱动设计真正成为读者具有竞争力的技能。
根据该学习路线,本书领域驱动设计的知识体系全景图如图 1-2 所示。
图1-2 本书领域驱动设计的知识体系全景图
1.3 领域驱动设计常见争议探讨
业界对于领域驱动设计存在不少的争议,本节针对一些常见的争议进行探讨。
1.3.1 领域驱动设计的适用范围
一直以来,业界流行着这样的观点:简单的系统不适合领域驱动设计,复杂的系统才适 合。事实真的如此吗?有什么量化的标准可以评价系统是否适合实施领域驱动设计吗?
如果一套实践理论在简单的场景中表现不佳,但在复杂的场景中表现较好,那么这套理论 在复杂场景下真的能够取得好的效果吗?笔者认为不能。复杂系统在拆分之后也由许多简单的 子系统构成,并且领域驱动设计在许多情况下也是通过将复杂系统拆分为简单的系统来解决业 务复杂性问题。
“简单的系统不适合领域驱动设计,复杂的系统才适合”的观点在业界盛传,笔者认为并 不是因为领域驱动设计在简单的系统中没有可行性,更多的是基于其他方面的考虑,例如学习 成本、研发效率、风险控制等。
领域驱动设计的学习门槛比较高,初学者需要理解大量的概念。假如大部分团队成员缺 乏实战经验,则往往需要组织团队成员进行培训。一方面,培训需要花费额外的成本。另一方 面,仓促的培训可能很难取得很好的效果。
领域驱动设计在实现过程中也存在一些可能影响研发效率的地方。例如,聚合根有一个 原则:一个事务操作只更新一个聚合根,跨聚合根的操作采用最终一致性。相比事务脚本的方 式一次性操作多张数据表,领域驱动设计为了保证跨聚合操作的最终一致性,需要投入大量的 研发资源以解决技术细节问题,很有可能给项目正常交付造成压力。
另外,目前领域驱动设计在实践过程中缺乏提效工具,导致给人一种笨重的感觉。
学习成本和研发效率这两个方面本身也给项目实施带来潜在的风险。此外,每个团队对领 域驱动设计的理解差异很大,如果团队缺乏领域驱动设计成功的经验,还有可能存在技术可行 性上的风险,例如,项目在实施过程中发现对某些架构层面的理解不到位,导致不得不返工。
另外,当构建一些简单的系统时,业务往往处在落地的初级阶段,团队整体缺乏领域专业 知识,给领域驱动设计带来实践障碍。事实上,在业务落地的初期,笔者非常推荐采用事务脚 本的方式进行面向过程的编程。事务脚本提供多表操作的能力,使得开发非常快捷(虽然也很 显得粗暴)。尽快交付开发成果,能帮助企业快速进行商业模式试错。
Vaughn Vernon 在其著作《实现领域驱动设计》的第 1 章介绍了一种“领域驱动设计计分 卡”的方式,得分在 7 分以上,就推荐考虑实施领域驱动设计。这种打分方式有一定的依据, 但是笔者会通过更简单、快捷的方式去判断是否适合实施领域驱动设计。笔者的判断方式为: 如果目标系统无法在一个数据库事务里进行跨聚合更新,那么直接选择领域驱动设计。
当采用分库分表、将某个业务服务切分到外部团队并存储到单独的数据库中时,没有办法 保证在一个数据库事务里完成跨聚合的数据更新,事务脚本在研发效率上的收益骤减。此时既 然无法满足一个数据库事务中操作多张数据表,那么应该果断选择领域驱动设计。领域驱动设 计要求一次数据库事务只能更新一个聚合,聚合之间要通过最终一致性保持一致,非常适合这 种场景。如果某个项目设计之初就需要分库分表,则一开始就可以实施领域驱动设计。
1.3.2 贫血模型与充血模型的选择
领域驱动设计的落地永远绕不开贫血模型和充血模型的争议。关于贫血模型和充血模型的 选择,将在 2.1 节中详细探讨。
贫血模型最终会导致 Service 层方法过度膨胀。领域驱动设计理论要求使用充血模型来建 模领域模型,如果采用贫血模型,那么对领域驱动设计落地的理解是不完整的。
笔者推荐使用充血模型进行领域驱动设计落地。
1.3.3 领域驱动设计落地的认知差异
业界对于领域驱动设计落地的认知主要有以下两种观点:观点一认为领域驱动设计只有战 略设计层面的落地;观点二认为领域驱动设计只关注战术落地。
- 观点一:领域驱动设计只有战略设计层面的落地
持这种观点的实践者认为,领域驱动设计只能进行战略层面的落地,战术层面的落地是行不通的。 这部分实践者主要是被领域驱动设计战术落地难的困境所吓倒,他们找不到正确的实现方 案,因此对战术设计持悲观的态度。 战略设计层面的实践无疑是具有极大价值的,至少在大方向上完成了限界上下文的划分和 子域的识别。然而,忽略战术设计会丢弃战术设计相关的良好实践,因此无法产出高质量的代 码。经常看到很多号称落地了领域驱动设计的项目,到最后又开倒车改成贫血分层架构,这正 是因为缺乏战术设计方面的努力。 读者也要理解,此类观点之所以在业界盛行,主要是因为领域驱动设计落地缺乏很好的案 例、规范和配套研发提效工具。这也是笔者目前正在致力的方向:写教程、给案例、出规范、 定标准、建生态、推工具。
- 观点二:领域驱动设计只关注战术落地
持这种观点的大部分人是刚接触领域驱动设计的实践者。由于他们对领域驱动设计理解 的深度不够,所以习惯性地从技术角度去理解领域驱动设计,这导致他们经常纠结于选用什么 架构、如何实现某个类、哪种方法适合放在哪里等细节问题。由于过分关注细节,因此忽略了 从整体上把握领域驱动设计,既不了解战略设计,也不理解战术设计,在实践的过程中束手束 脚、举步维艰,往往会放弃拥抱领域驱动设计。
本书1.2.2 节正是为这些对领域驱动设计缺乏全局理解、缺少学习方向的初学者准备的。
1.3.4 领域驱动设计的技术选型
领域驱动设计是一种与技术无关的开发方法,不管使用什么开发语言、采用什么技术框 架,都不会影响领域驱动设计的实施。
在技术选择方面,只需使用当前业界常用的开源组件,就可以实现领域驱动设计。
本书案 例的技术选型见表 1-1。
//todo
本书的一个特点就是使用常见、通用的开源技术组件实现领域驱动设计,不将领域驱动设 计落地与冷门偏门、学习成本高的组件进行捆绑销售。 用于实现领域驱动设计的应用架构有很多,例如,经典的四层架构、六边形架构等,很 多初学者仅仅在选择架构上就感到眼花缭乱。本书将会在第 2 章中专门讲解领域驱动设计的应 用架构,并带领读者完整地推导出自己的应用架构。本书所有随书案例均基于这个应用架构 开发。
1.3.5 领域驱动设计与面向对象编程
笔者经常被提问关于领域驱动设计与面向对象编程的关系问题,例如:“既然已经有了面 向对象编程,为什么还需要领域驱动设计?”“领域驱动设计将面向对象编程颠覆了吗?”“面 向对象编程经常讲的 SOLID 原则在领域驱动设计下还适用吗?”等。
既然已经有了面向对象编程,为什么还需要领域驱动设计呢?首先,两者的关注点不一 样。面向对象编程强调的是对象的行为和状态,而领域驱动设计不仅关注对象的行为和状态, 还会关注业务的边界,将模型对象分配到对应的边界中,并定义不同边界的模型的协作方式, 使模型之间的交互过程更清晰、依赖关系耦合更少。其次,领域驱动设计提供了一些实践经 验和工具,帮助开发团队更好地理解和应对复杂业务场景。例如,领域驱动设计中的聚合、实 体、值对象等概念能够帮助开发者更好地组织代码结构,降低系统的复杂度。
领域驱动设计将面向对象编程颠覆了吗?并不是。领域驱动设计并不是要取代面向对象编 程,而是在面向对象编程的基础上,站在业务的层面上进一步完善和优化软件设计方法。事实 上,领域驱动设计中的许多概念和原则都与面向对象编程密不可分。例如,聚合、实体、值对 象等概念都基于面向对象编程中的类和对象。从面向对象的三大特征(继承、封装、多态)可 以看出,面向对象编程研究的是对象个体,而从领域驱动设计的限界上下文、子域等理论可以 看出,领域驱动设计研究的是对象的组织和组织间的协作。
如何理解领域驱动设计和面向对象编程的关系呢?举一个汽车厂的例子,有一家可以同 时生产卡车和公交车的汽车厂,起初在一个车间里同时生产卡车和公交车。这种将零件组装成 整车的过程就是面向对象编程。后来,厂家发现同时在一个车间生产两种车很容易造成人员安 排混乱、设备升级困难(因为要兼顾两种类型的车)等问题,于是分别梳理这两种车的生产过 程,将其安排到不同的车间中生产,各自只生产一种类型的车,这个过程就是领域驱动设计。 拆分后的车间在生产卡车或者公交车时,依旧是将零件装成整车,也就是说,还是面向对象编 程。所以,面向对象编程是领域驱动设计的基础,领域驱动设计是对面向对象编程的拓展和 完善。
SOLID 原则在领域驱动设计下还适用吗?答案是肯定的。SOLID 原则包括的单一职责原 则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则在领域驱动设计中依旧是适用 的。领域驱动设计同样可以从 SOLID 原则中受益,提高代码的可读性、可维护性、可扩展性 和可重用性。例如,在领域驱动设计中,聚合就是一个高度内聚、低耦合的业务单元,它与单一职责原则是一致的;而在实现领域服务时需要遵循开闭原则,对扩展开放而对修改关闭,以 便领域服务能够适应多变的需求场景。
1.3.6 不要过度迷信领域驱动设计
领域驱动设计并不总是最好的解决方案,学了领域驱动设计也不能保证开发者一定能合理 地划分业务边界。领域驱动设计只是一种设计方法,它并不能自动地划分业务边界,也没有提 供定量的实践标准。因此,不要寄希望于学习了领域驱动设计之后,就一定能完美地划分业务 边界。 划分业务边界需要对业务有深入的了解,需要与领域专家紧密合作,需要对各种业务场景 有全面的认知。
实际上,“合理的业务边界”本身就是伪命题,没有放之四海而皆准和一成不变的业务边 界,只能寻求当下合理的业务边界。
至于该如何使用领域驱动设计去划分业务边界,以下是笔者的一些建议。
首先,加强对业务的理解。这包括了解业务的核心概念、业务流程、业务规则等。只有对 业务领域有全面的认识,才有可能合理地划分业务边界。在了解业务领域的过程中,需要与领 域专家紧密合作。领域专家是对业务最了解的人,他们能够提供关于业务流程、规则等方面的 详细信息,帮助我们更好地理解业务需求。
其次,活用领域建模和战略设计。划分业务边界的过程也是将一个大型复杂的业务领域划 分为多个小而简单的子领域的过程。通过领域建模,可以得到具体的领域模型,进而得到明确 的限界上下文;通过战略设计,从这些限界上下文中精炼得到子域。在划分子领域时,考虑每 个子领域所包含的核心概念、业务流程、规则等方面,并将其与其他子领域进行区分。明确限 界上下文、子域的范围,可以帮助我们更好地划分业务边界。
最后,不断迭代优化。业务是不断演进和调整的,划分业务边界也是一个不断迭代优化 的过程。在实际应用中,可能会发现一些子领域之间的关系不太清晰,或者某些子领域需要进 一步拆分。在这种情况下,要及时调整和优化,以确保划分出来的子领域能够更好地支持业务需求。
附录部分
Ⅰ 学习交流
- 读者交流微信群
欢迎加入 DDD 交流群。微信扫以下二维码添加作者微信,标注“DDD”,好友申请通过后拉您进群。
- 微信公众号
本书专属公众号“悟道领域驱动设计”。
Ⅱ 版权声明
- 本作品代码部分
采用 Apache 2.0 协议进行许可。
遵循许可的前提下,你可以自由地对代码进行修改,再发布,可以将代码用作商业用途。但要求你:
署名:在原有代码和衍生代码中,保留原作者署名及代码来源信息。
必须提供作者的署名以及本作品的链接(http://ddd.feiniaojin.com/)
保留许可证:在原有代码和衍生代码中,保留 Apache 2.0 协议文件。
- 本作品文档、图片等内容部分
采用署名-非商业性使用-禁止演绎 4.0 国际 (CC BY-NC-ND 4.0 DEED)进行许可。
在遵守以下条件的前提下:
署名: 您必须给出 适当的署名 ,提供指向本许可协议的链接,同时 标明是否(对原始作品)作了修改 。您可以用任何合理的方式来署名,但是不得以任何方式暗示许可人为您或您的使用背书。
引用本作品任何内容时,必须提供作者的署名以及本作品的链接(http://ddd.feiniaojin.com/)
非商业性使用: 您不得将本作品用于 商业目的 。
在媒体、自媒体平台(包括但不限于微信公众号、头条号等)转载、二次创作、发表等行为将被视为商业应用,必须取得作者的授权。
禁止演绎: 如果您 再混合、转换、或者基于该作品创作 ,您不可以分发修改作品。
基于本作品任何内容,进行任何形式(包括但不限于文章、视频、语音、有声书等)的二次创作(包括翻译为其他语言),都必须取得作者的授权。
没有附加限制: 您不得适用法律术语或者 技术措施 从而限制其他人做许可协议允许的事情。
您可以自由地:
共享: 在任何媒介以任何形式复制、发行本作品。
只要你遵守许可协议条款,许可人就无法收回你的这些权利。