领域驱动设计(DDD)基本概念

  • ~4.01K 字
  1. 1. 什么是微服务
  2. 2. DDD 与微服务
  3. 3. 领域与领域模型
  4. 4. 通用语言与界限上下文
  5. 5. 实体类与值对象
  6. 6. 聚合与聚合根
  7. 7. 领域服务与应用服务
  8. 8. 领域事件与集成事件
  9. 9. 推荐书籍

在软件开发中,我们常常面临这样的困境:系统随着需求迭代逐渐变得臃肿、难以维护,最终沦为”屎山代码”。业务逻辑散落各处、模块边界模糊,修改一处牵一发而动全身——这就是软件退化(Software Erosion)。

如何在业务快速变化中保持架构的灵活性,防止系统走向混乱?领域驱动设计(Domain-Driven Design, DDD)正是为此而生。

DDD 是一种演进式架构思想,通过通用语言、界限上下文、聚合等核心概念,让代码结构与业务领域保持一致。它不仅是微服务架构设计的指导思想,更是构建可演进、可维护系统的有效方法论。

什么是微服务

传统软件项目大多采用单体架构,所有功能模块集成在一个应用程序中。单体结构有结构简单、部署简单等优点,但有如下缺点:

  • 代码间耦合严重,模块间相互依赖,牵一发而动全身。
  • 项目只能采用单一语言和技术栈
  • 扩展性差,只能对整体进行服务器扩容,无法对单一模块进行服务器扩容
  • 任何改动都需要重新部署整个应用

微服务架构把项目拆分多个应用程序,每个服务独立开发、部署和运行。

微服务架构有如下优点

  • 高内聚低耦合,业务逻辑清晰,对其他微服务依赖低
  • 不同微服务可用不同语言和技术栈
  • 一个微服务运行不会影响其他微服务
  • 一个服务出错不会导致整个系统崩溃
  • 可针对高负载服务单独扩容

缺点

  • 运维工作带来更大挑战,需要管理多个服务的部署、监控和日志
  • 需要处理服务间通信、容错、负载均衡等问题
  • 跨服务的事务处理和数据一致性难以保证

微服务架构误区

设计不好的微服务架构中,微服务之间调用关系非常复杂,一个请求经过七八层微服务调用,这样的设计不仅导致系统间耦合严重,还使服务器端处理效率地下。

微服务架构应该是进化而来的。在进行系统架构设计时,我们应该认真思考“这个项目真的需要微服务架构吗”。

DDD 与微服务

DDD 为微服务拆分提供了理论指导:

  • 限界上下文用于划分服务边界
  • 聚合用于设计服务粒度
  • 领域事件用于服务间解耦通信

通过 DDD 的指导,微服务拆分变得有章可循,避免服务划分过细或过粗的问题。

领域与领域模型

领域(Domain) 是指一个组织所从事的业务范围和内容。例如电商领域包括商品管理、订单处理、支付结算、物流配送等业务。

领域模型(Domain Model) 是领域确定后,从领域内对象建模抽象出模型的概念。建模是DDD中非常核心的事情,一旦定义出领域模型,我们就可以用领域模型驱动项目开发。

DDD 将系统划分为四层:

  • 用户接口层(User Interface)(表现层):
    • 负责处理所有外部流量的入口,包括用户界面(如Web前端)、API接口(如REST、gRPC)以及消息队列等。
    • 主要职责是接收用户请求和展示数据,并将数据转换为应用层能处理的格式。
  • 应用层(Application Layer):协调领域对象完成用户任务,不包含领域逻辑
  • 领域层(Domain Layer):包含核心业务逻辑和数据模型,是系统的核心
  • 基础设施层(Infrastructure Layer):为其他层提供技术支持,如数据持久化、消息队列、外部服务调用等

领域层是 DDD 的核心,所有业务规则都应该在领域层实现,而不是散落在应用层或基础设施层。

通用语言与界限上下文

通用语言是指团队中所有人(包括开发人员、产品经理、业务专家)都使用相同的术语来描述业务概念。通用语言应该体现在代码、文档、对话中。

例如,在电商系统中:

  • 不要用 User 来指代买家,应该明确使用 Customer(客户)或 Buyer(买家)
  • 不要用 Order 的状态字段 status=5 表示已完成,应该使用 OrderStatus.Completed

通用语言的好处:

  • 减少沟通成本和理解偏差
  • 让代码更具可读性和可维护性
  • 业务变化时,代码能够快速响应

界限上下文是对业务领域的明确边界划分。在不同的界限上下文中,同一个术语可能有不同的含义。

例如,”商品”在不同上下文中的含义:

  • 商品目录上下文:商品包含名称、描述、分类、图片等信息
  • 库存上下文:商品只关注 SKU、库存数量、仓库位置
  • 订单上下文:商品包含价格、购买数量、优惠信息

界限上下文帮助我们:

  • 明确服务边界,为微服务拆分提供依据
  • 避免统一大模型导致的复杂性
  • 让不同团队可以独立开发各自的上下文

实体类与值对象

在领域模型中,实体(Entity)和值对象(Value Object)是构成领域模型的基本元素,二者的核心区别在于是否具有唯一标识。

实体是具有唯一标识的领域对象,其生命周期中标识保持不变,即使属性发生变化,也依然是同一个实体,业务上有独立存在意义。

例如:Order(订单)、Customer(客户)

1
2
3
4
5
6
public class Order
{
public Guid OrderId { get; private set; }
public DateTime CreatedTime { get; private set; }
// 其它业务属性...
}

值对象是通过属性值来定义的对象,没有唯一标识,属性完全相同则视为同一个对象,一般生命周期短。

例如:Address(地址)

1
2
3
4
5
6
public class Address
{
public string City { get; }
public string Street { get; }
// 值对象只关心内容,通常设计为不可变
}

实体和值对象的区分有助于合理建模、简化业务逻辑。

聚合与聚合根

当领域模型中的实体和值对象数量增多时,如何保证它们之间的一致性?DDD 通过聚合(Aggregate) 和聚合根(Aggregate Root) 解决这一问题。

聚合是一组紧密关联的实体和值对象的集合,它们作为一个整体负责维护业务规则(不变量),确保数据一致性。

特征:

  • 一组相关对象(实体和值对象)的集合,作为数据的一致性边界
  • 聚合内部对象只通过聚合根与外界交互
  • 聚合保证内部业务规则和事务一致性

该聚合的不变量是 “订单总金额 = 所有订单项金额之和”,无论添加、删除订单项,这一规则必须始终成立。

聚合根是聚合的 “入口点”,是聚合中唯一允许外部直接访问的实体,负责协调聚合内的对象,维护聚合的完整性。

特征:

  • 是聚合中唯一具有全局唯一标识的实体
  • 外部对象只能通过聚合根的 ID 引用聚合,不能直接访问聚合内的其他实体或值对象
  • 聚合根负责聚合内对象的生命周期管理(创建、修改、删除)

示例:
订单聚合(Order为聚合根) ≈ Order(订单)+ OrderItems(订单行项)+ ShippingAddress(收货地址)。

聚合设计原则:

  • 单个聚合尽量小,只解决核心业务一致性
  • 跨聚合一致性用最终一致性、领域事件等解决

领域服务与应用服务

在 DDD 的分层架构中,领域层和应用层分别通过领域服务和应用服务处理业务逻辑,但二者的职责有明确区分。

领域服务是领域层的组件,用于封装不属于单个实体或值对象的领域逻辑。当一个业务操作涉及多个实体 / 值对象协作时,适合用领域服务。

特征:

  • 包含纯领域逻辑,与技术实现无关
  • 通常以动词命名(如CalculateOrderDiscount、SettleAccount)
  • 输入和输出多为领域对象(实体、值对象、聚合根)

应用服务是应用层的组件,负责协调领域对象完成用户任务,不包含领域逻辑,主要承担流程编排的角色。

特征:

  • 面向用户用例(如 “创建订单”“取消支付”),以用例命名(如CreateOrderService)
  • 调用领域层(实体、聚合根、领域服务)完成业务,自身不处理具体业务规则
  • 处理跨切面关注点(如事务管理、权限校验、日志记录)
  • 负责数据转换(将用户接口层的 DTO 转换为领域对象,再将领域对象转换为 DTO 返回)

二者的区别在于领域服务处理 “如何做”(业务规则),属于领域核心,应用服务处理 “做什么”(流程步骤),属于协调层。

领域事件与集成事件

在复杂领域中,对象间的协作往往需要 “事件驱动” 来解耦。DDD 通过领域事件和集成事件实现不同范围的事件通信。

领域事件用于表达领域模型中某个有意义的事件刚刚发生,解耦了聚合间、模块间的强依赖关系。

特征:

  • 属于领域层,由聚合根在状态发生关键变化时发布(如订单创建、支付完成)
  • 命名格式通常为 “[实体][动作] Event”(如OrderCreatedEvent、PaymentCompletedEvent)
  • 包含事件发生的时间、相关实体 ID 等关键信息
  • 上下文内的其他组件(如领域服务、聚合根)可订阅事件并作出响应

集成事件是跨限界上下文(微服务) 的事件,用于实现不同上下文间的异步通信,解决服务间的耦合问题。

特征:

  • 属于基础设施层或应用层,由应用服务将领域事件转换而来
  • 包含跨服务通信所需的最小信息(避免传递领域对象细节)
  • 通过消息队列(如 RabbitMQ、Kafka)等中间件传递,保证可靠性
  • 用于触发其他上下文的业务操作(如订单支付后通知物流服务发货)

领域事件是集成事件的源头,集成事件是领域事件的 “跨域版本”。通过这种分层设计,既保证了限界上下文内的高内聚,又实现了跨上下文的低耦合协作。

推荐书籍

  • 《ASP.NET Core 技术内幕与项目实战》-杨中科
  • 《领域驱动设计:软件核心复杂性应对之道》-Eric Evans,「领域驱动设计之父」
  • 《实现领域驱动设计》-Vaughn Vernon