架构师必知必会之 DDD 的核心要点回顾|架构师面试必背手册

点击上方蓝字,关注灸哥!




大家好,我是灸哥,最近有点忙,在开始今天一堆事情之前,和大家聊聊 DDD 的一些核心要点,方便大家对核心概念的整体记忆和把握~

领域驱动设计,Domain-Driven Design,简称 DDD,由 Eric Evans 在其 2003 年《领域驱动设计:软件核心复杂性应对之道》中首次提出并进行了系统性地阐述,作者认为 DDD 是一种开发复杂软件的方法。

所有要学习 DDD 的同学我依旧还是建议从这本书开始,虽然已经过去了 20 年,看完这本书之后,再考虑其他的,文末我也会把我在 DDD 学习过程中看过的不错的书推荐给大家~

DDD 是一种软件设计方法论,它强调以业务领域为核心来构建软件架构,是为了解决服务业务领域的软件建模问题,随着企业应用的复杂度和规模的不断增长,传统的以数据为中心或者以技术为中心的设计方法已经难以应付了。

DDD 通过引入领域模型、通用语言、限界上下文等概念,帮助开发团队更好地理解和应对业务领域的复杂度。

  1. DDD 强调业务领域的核心地位,使得开发出的系统更加贴近业务需求,易于理解和维护。

  2. DDD 提供了一套完整的建模工具和方法论,有助于开发团队形成统一的语言和思维模式,提高沟通效率。

  3. DDD 的模块化设计思想有助于提高系统的可扩展性和可维护性。

  4. DDD 的实施难度比较大,需要开发团队具备一定的业务领域知识和经验。

  5. DDD 的建模过程相对复杂,需要投入较多的时间和精力

  6. 对于某些简单或者变化较少的业务领域,采用 DDD 会显得过于繁重。

01 领域模型

DDD 强调首先要理解业务领域,然后构建一个能够准确反应业务领域的模型,这个模型不仅包括业务实体和他们之间的关系,还包括业务过程中的业务规则和逻辑

这个模型就是领域模型,它是关于某个特定业务领域的软件模型,用于对业务领域进行建模,以更好地理解业务和问题域。

领域会被进一步划分为子领域,每个子领域对应一个更小的问题或者业务范围,这种划分有助于降低业务理解和系统实现的复杂度,领域模型就是对这些子领域进行建模的结果,它描述了子领域内的实体、值对象、聚合根、领域服务、领域事件等概念及其之间的关系。

  1. 实体:具有唯一标识符的领域对象,在业务领域中具有持续的存在性

  2. 值对象:描述实体的属性或者状态的对象,没有唯一标识符,且不可变

  3. 聚合根:是一个聚合的根实体,负责协调聚合内的其他实体和值对象的行为

  4. 领域服务:在领域中执行的操作或者行为的服务,通常是无状态的

  5. 领域事件:标识在领域中发生的事件的对象,用于触发其他领域的行为

02 通用语言

通用语言是指在整个软件生命周期内,由领域专家、开发人员和其他角色共同使用的一种统一的、明确的语言,这种语言以业务领域为核心,贯穿于 DDD 的战略设计和战术设计的各个层面。

通用语言重要性

  1. 消除沟通障碍:在软件开发过程中,领域专家和开发人员往往因为背景知识、术语理解等方面的差异而产生沟通障碍,通用语言通过明确术语和概念的定义,使得双方能够使用同一种语言来描述和理解业务领域,从而消除沟通障碍

  2. 提高开发效率:通用语言能够帮助开发人员更快地理解业务领域,减少在需求分析、设计、开发等阶段中的反复沟通和确认,有助于缩短开发周期,提高开发效率

  3. 保证代码质量:通用语言能够确保开发人员编写的代码与业务需求保持一致,通过通用语言中的术语和概念来命名代码中的类、方法、变量等,可以让代码更加易于理解和维护

建立通用语言步骤

  1. 识别领域对象:首先需要识别出业务领域中的关键对象,如实体、值对象,这些对象将构成通用语言中的名词部分,这一部的难度和能力要求都很高

  2. 定义术语和概念:针对识别出的领域对象,需要明确定义其术语和概念,包括对象的名称、属性、行为以及与其他对象的关系等,这些构成通用语言中的动词和形容词部分

  3. 建立限界上下文:为了避免术语和概念的歧义,需要建立限界上下文来明确通用语言的适用范围

  4. 持续迭代和完善:通用语言的建立也是一个持续迭代和完善的过程,随着业务的发展和变化,需要不断地对通用语言进行调整和优化,以确保其始终与业务需求保持一致

03 限界上下文

限界上下文是指在一个特定的业务领域内,通用语言所描述的对象、术语和概念具有明确的含义和边界,这个边界定义了模型的适用范围,使得团队所有成员能够明确地指导什么应该在模型中实现,什么不应该在模型中实现。

限界上下文重要性

  1. 明确业务边界:通过定义限界上下文,可以明确划分出不同业务领域的边界,有助于避免不同领域之间的概念混淆和术语歧义,使得每个领域都能保持其独立性和一致性,对应到研发中,如果应用架构设计合理的化,可以简单地把一个应用等同于一个限界上下文

  2. 促进团队协作:限界上下文为开发团队提供了一个明确的工作边界,团队成员可以清楚地了解自己所负责的领域范围,从而更好地进行任务分配和协作,有助于降低团队之间的沟通成本,提高开发效率

  3. 保证系统一致性:限界上下文有助于维护系统的业务一致性,在每个限界上下文中,领域模型、通用语言和业务规则都得到了明确的定义和约束,这可以确保系统在各个层面上能够保持一致,降低系统的业务复杂度

  4. 支持技术实现:限界上下文为技术实现也提供了指导,在系统设计和开发过程中,可以根据限界上下文的划分来确定系统的模块划分、接口定义等,有助于保证系统的技术一致性,降低系统的技术复杂度

识别限界上下文步骤

  1. 理解业务领域:首先需要深入理解业务领域,识别出其中的关键业务对象、业务流程和业务规划,有助于为后续的限界上下文划分提供基础

  2. 划分业务领域:根据业务相关性、耦合强弱程度等因素,将业务领域划分为多个子领域,每个子领域对应一个相对独立的业务范围,具有一定的业务内聚性

  3. 定义限界上下文:针对每个子领域,定义其对应的限界上下文,明确每个限界上下文中的对象、术语和概念的含义和边界,以及与其他限界上下文之间的关系和交互方式等

  4. 持续优化和调整:随着业务的发展和变化,需要不断地对限界上下文进行优化和调整,可能设计到重新定义限界上下文的边界、调整对象之间的关系以及引入新的通用语言

04 聚合

聚合是指将一组具有业务相关性的领域对象组合在一起,形成一个整体的概念。这个整体具有明确的业务含义和边界,并且可以通过聚合根来进行访问和操作。

聚合重要性

  1. 业务一致性:聚合内的对象共同保证业务规则的一致性,当对聚合中的某个对象进行操作时,聚合根会根据业务规则协调内部对象的状态变化,以保持数据的一致性

  2. 封装复杂性:通过将具有业务相关性的对象组合在一起,聚合可以封装内部的复杂性,外部对象只需要通过聚合根与聚合进行交互,而不需要了解聚合内部的详细实现,有助于降低系统的复杂性和提高可维护性

  3. 明确责任边界:聚合具有明确的责任边界,每个聚合都有自己的业务职责和范围,负责处理与之相关的业务逻辑,有助于避免不同聚合之间的职责交叉和混乱

聚合遵循原则

  1. 选择合适的聚合根:每个聚合都需要一个聚合根,它负责协调和管理聚合内部的对象。聚合根具有全局唯一的标识,并且外部对象只能通过聚合根来访问和操作聚合

  2. 保持聚合的完整性:聚合是一个整体的概念,需要保持其完整性。在对聚合进行操作时,应该遵循业务规则,确保聚合内部对象的状态变化是一致的

  3. 设计小聚合:为了降低系统的复杂性和提高可维护性,应该尽量设计小的聚合。每个聚合只关注一个核心业务概念,并且只包含与之紧密相关的对象

  4. 避免跨聚合引用:为了保持聚合的独立性和完整性,应该避免跨聚合引用。不同聚合之间的对象不应该直接相互引用或调用,而应该通过领域事件或应用服务来进行间接的交互业务一致性:聚合内的对象共同保证业务规则的一致性,当对聚合中的某个对象进行操作时,聚合根会根据业务规则协调内部对象的状态变化,以保持数据的一致性

05 仓储

仓储是用于封装数据访问逻辑的接口,主要负责领域模型的持久化和检索

仓储类似于仓库管理员的角色,但它管理的不再是具体的货物,而是领域模型中的聚合,在层次结构上,位于领域模型和数据模型之间,为两者提供一个边界,并隔离它们,使开发人员可以专注与领域模式,不需要过多考虑如何进行持久化。

仓储和数据访问层区别在于仓储限定了只能通过聚合根来持久化和检索领域对象,确保了所有的改动和不变性都由聚合来处理。通过隐藏聚合持久化和检索的底层技术细节,实现领域层的持久化无关性,这就意味着领域层不需要知道如何具体地持久化领域对象。

06 应用服务

应用服务在 DDD 中负责协调领域对象来执行特定的应用程序任务。它位于应用层,作为展现层和领域层之间的桥梁,用来表达用例和用户故事的主要手段,并通过应用服务接口来暴露系统的全部功能。

应用服务在整个系统中主要负责编排和转发,它将要实现的功能委托给一个或多个领域对象来实现,而应用服务本身只负责处理业务用例的执行顺序以及结果的拼装,这种方式隐藏了领域层的复杂性及其内部实现机制。

应用层相对比较薄,除了定义应用服务之外,在该层可以进行安全认证、权限校验、持久化事务控制等操作。此外,应用层还可以用于发送基于事件架构模式的消息给其他系统,或者用于创建其他消息形式发送给客户等,比如 Email 等。

07 领域服务

领域服务属于领域层,用于执行特定于某个领域的任务。它表示一个无状态的操作,用于实现特定于某个领域的任务,这些任务通常不适合放在实体或者值对象上,因为他们可能涉及多个领域对象的协作和转换。

领域服务适用场景

  1. 执行一个显著的业务操作流程,比如计算订单金额,需要合并各种优惠等规则,这些操作不是实体或者值对象的职责,是需要独立出来作为一个服务来处理的

  2. 对领域对象进行转换,将一个领域对象转换为另一种类型或者格式,此时需要领域服务来完成

  3. 以多个领域对象作为输入参数进行计算,并产生一个值对象作为结果,此时需要领域服务来协调不同领域对象之间的交互和数据流转

领域服务特点

  1. 领域服务通常是无状态的,这就是意味着它不保存任何与特定操作相关的状态信息,每个服务调用都是独立的,不依赖于先前的调用

  2. 领域服务应该与通用语言保持一致,他们的命名和操作应该反应业务领域的术语和概念

  3. 领域服务可以处理业务规则的验证、协调不同对象的操作和数据一致性等。它可以作为跨领域操作的协调者,负责处理不同对象之间的交互和数据流转

领域事件

领域事件是领域模型中的一部分,用于表示在领域中发生的有意义的事情,表示了领域状态的变化或者业务操作的执行,当某个重要事件在业务领域内发生时,领域事件会被发布,以便其他部分的应用程序可以知道并做出相应的反应。

领域事件用于实现业务解耦和形成完整的业务闭环,通过将领域中所发生的活动建模成一系列的离散事件,可以实现不同部分的应用程序之间的松耦合,一个领域事件的触发可以进一步推动业务流程的进行,形成完整的业务闭环。

领域事件还可以用于维护领域模型的完整性和一致性,当领域中的状态发生变化时,通过发布领域事件,可以确保相关部分的应用程序得到通知,并做出相应的调整,以保持数据的一致性。

定义领域事件的场景

  1. 可以通过捕捉业务、需求人员或者领域专家口中的关键词来识别领域事件,比如,当 XXX 发生时,如果 XXX 就 XXX,这些关键词可能意味着一个领域事件的触发条件或者后续动作

  2. 领域事件必须是对业务有价值的,并且有助于形成完整的业务闭环,它们可以是业务流程中的一个步骤,比如提交订单等,也可以是定时发生的事件,比如每日定时对账完成,还可以是一个事件发生后引发的后续动作,比如用户连续输错密码后锁定账户

领域事件实现方式

在实现领域事件时,需要考虑事件的构建、发布和订阅机制,事件的构建需要明确事件的触发条件、事件所携带的数据以及事件的类型等信息。事件的发布需要将事件通知给感兴趣的部分应用程序,可以通过事件总线或消息队列等方式进行,事件的订阅需要定义哪些部分的应用程序对哪些类型的事件感兴趣,并在事件发生时进行相应的处理。

DDD 不仅是一项技术,更是一项艺术,希望大家可以很好地掌握它~

Eric 提出 DDD 的原书中文版


10 年后的两本 DDD 也值得阅读




长按二维码识别关注

关注灸哥

了解更多


你的赞和在看,我统统都要

请使用浏览器的分享功能分享到微信等