小白也能看懂的DDD基本介绍

124 阅读8分钟

DDD

什么是DDD?

有一天小明带领了一个开发团队构建了一个软件,软件分为三层:表示层,业务逻辑层,基础设施层。最开始,大家各司其职,开发效率非常高。但是随着时间的流逝,应用逻辑的膨胀,维护软件变得越来越难,业务逻辑越来越混乱。于是代码库就成了传说中的‘屎山代码’,这一次,重生归来的小明决定换一个架构模式。

Domain-Driven Design (DDD) is a software development philosophy that emphasizes the importance of understanding and modeling the business domain. It is a strategy aimed at improving the quality of software by aligning it more closely with the business needs it serves.

DDD,全称领域驱动设计(Domain-Driven Design),是一种围绕业务需求而非技术细节构建软件的设计方法。它的核心是聚焦于领域或特定的业务环境,创建清晰的逻辑边界,解决传统架构耦合性强、后期难以维护的问题。

DDD由什么组成?

领域驱动设计必定是由多个领域及其模块构建而成,想要了解DDD,必须先了解一般的DDD是由什么组成的。

领域

领域是系统服务的业务范围,软件要解决的核心问题即在此范围内。它是业务层的最高级别。

子领域

一个领域通常包含多个子领域,每个子领域对应不同的业务功能。

限界上下文(Bounded Contexts

在复杂系统中,每个业务领域可能涉及不同方面,且各有独立的逻辑。限界上下文为系统的特定领域模型设定逻辑边界,每个上下文通过接口进行交互。

比如,在电商系统中,订单、支付、用户等不同模块各有自己的限界上下文,彼此独立,又可以通过接口和事件进行协作。

实体(Entities

实体指的是具有唯一标识的业务对象,尽管其状态可能发生变化,但其身份不变。

比如用户在系统中可以更改名字或地址,但其唯一标识(如用户ID)保持不变。

值对象(Value Objects

值对象是通过属性而非独特标识表示的业务对象,通常不可变,可自由替换为属性相同的另一个实例而不改变系统状态。

比如地址作为值对象可包含收货地址详细信息,但无单独的身份。

聚合(Aggregates

在复杂的领域,实体与对象一般有着千丝万缕的关系。而聚合通过将实体与对象集结起来形成一个单元。每个聚合都有一个根实体(root entity),也称为聚合根,与聚合相关的所有交互都是通过它发生的,通过唯一的入口来维护一致性,确保聚合内的逻辑符合业务规则。

例如,订单聚合中可能包含订单实体、商品列表、支付信息等数据,订单实体作为聚合根,确保订单数据的一致性。

领域事件(Domain Events

领域事件是领域模型中用于表示某种业务状态的变化。领域事件通常在事件驱动的架构中使用,用于通知其他模块发生了重要的变化。它能帮助系统模块间解耦。

比如“订单创建完成”事件可以通知支付模块和库存模块,使它们执行相应的后续操作。

领域服务(Domain Service)

领域服务是领域模型中用于处理跨实体的业务逻辑复杂操作的服务。它通常代表了一些无状态的操作,不属于特定的实体或值对象,但需要在领域层执行,确保多个实体间的业务逻辑一致性,并在领域模型中保持逻辑的清晰划分。

比如配送费计算、订单优惠计算等涉及多个领域的逻辑,可以通过领域服务来实现。

案例分析

接着我们以美团为例,分析他的DDD设计:

  • 领域划分:美团根据业务功能划分不同领域,例如外卖、到店、酒店等,每个领域负责独立的核心业务流程。

  • 子领域划分:例如,外卖领域包括用户下单、商家接单、配送等流程;到店领域包含用户购买代金券、预约座位;酒店领域包含酒店预订、房型管理等。

  • 限界上下文:每个领域进一步划分为多个限界上下文,各上下文专注于具体业务功能,相互独立。例如外卖领域包括:

    • 订单上下文:管理订单生命周期,包括创建、修改、取消等。
    • 配送上下文:处理配送调度、骑手分配、路线规划等。
    • 支付上下文:管理支付、退款等。
    • 商家上下文:管理商家信息、菜单、折扣活动等。
  • 实体和值对象:在限界上下文中定义核心业务对象:

    • 实体:具有唯一标识的对象,如订单上下文中的订单实体,每个订单有唯一 ID。
    • 值对象:无唯一标识,用于表示细节信息,如订单中的“地址”。
  • 聚合:将多个实体集合为一个单元。例如,订单聚合可能包括订单实体、商品列表、支付信息等,根实体为“订单”。

  • 领域服务:用于处理跨实体的业务逻辑。

    • 配送费计算服务:根据距离和时间计算配送费,涉及订单和配送上下文。
    • 优惠计算服务:计算订单优惠价格,涉及商家上下文、订单上下文和用户优惠券。

DDD怎么进行交互呢?

从上文知道,DDD是构成了领域与上下文的方式将程序进行解耦,但是他们之间又是如何交互的呢?

1. 领域之间的交互

不同领域之间通过服务层领域事件进行交互。服务层定义业务接口,领域事件捕捉和通知重要变化。

实现方式:
  • 服务层:在不同领域之间定义业务接口,通过 领域服务(Domain Services)来实现。例如,订单领域可能需要通过支付领域的服务来验证支付状态。

  • 领域事件:领域事件用于捕捉领域内部发生的重要变化,并广播给其他领域监听。比如,支付领域完成支付后,可以通过发布“支付成功”事件通知订单领域,订单领域根据事件更新订单状态。

    比如订单领域创建订单后,可以通过事件通知支付领域完成付款,保持两个领域间的解耦。

2. 子领域之间的交互

子领域是一个更小范围的业务模块,通常隶属于同一个领域,但也可能涉及到不同领域的交互。子领域之间的交互一般也是通过 领域事件共享的领域服务 来实现。

实现方式:
  • 领域事件:子领域之间的交互通常通过事件驱动的方式实现。例如,一个子领域的操作(如创建订单)可能会触发另一个子领域的行为(如支付子领域处理付款)。

    例如,订单子领域创建订单后,发布“订单创建”事件,库存子领域监听该事件并处理库存更新。

  • 领域服务和API:如果子领域之间的交互需要进行同步操作,可以通过定义 跨子领域的服务 来完成。一个子领域可以调用另一个子领域暴露的接口或服务来实现必要的功能。

    例如,订单子领域需要查询支付状态时,可以通过调用 支付子领域 的服务来获取状态信息。

3. 限界上下文之间的交互

限界上下文之间的交互是 DDD 中的核心概念。由于每个上下文内有独立的模型和语言,它们之间需要通过明确的接口、协议或事件来进行交互。

实现方式:
a. 共享内存(Shared Kernel)
  • 共享内存是指两个上下文共享一部分模型或数据结构。它们会在一个共同的空间内协作,一起维护一个共享的业务模型。例如,多个上下文都使用统一的用户模型来表示用户。

    例如,在 订单上下文支付上下文 中,可能会共享用户信息模型。

b. 上下文映射(Context Mapping)
  • 上下文映射是一种概念,指明不同上下文之间是如何进行交互和数据共享的。常见的上下文映射策略有:

    • 一致性映射(Shared Kernel) :多个上下文共享一部分模型和逻辑。

    • 防腐层(Anti-Corruption Layer, ACL) :当一个上下文依赖于另一个上下文的外部模型时,防腐层会负责将外部上下文的模型转换为本地上下文的模型,防止外部模型污染本地领域模型。

    例如,支付上下文中的支付模型可能需要通过防腐层来映射到订单上下文中的订单模型。