领域驱动设计(DDD)核心概念

领域驱动设计(Domain-Driven Design,DDD)由 Eric Evans 在其经典著作中提出,是一套应对复杂业务建模的软件方法论。其核心在于:将业务领域的知识深度融入软件模型,而非让技术实现主导架构。本章将逐一拆解 DDD 的五大核心构造块:实体、值对象、聚合、领域事件与限界上下文,并揭示它们如何在 NestJS 限流熔断场景中协同落地。

1. 实体(Entity)

实体是具有唯一标识且生命周期持续变化的对象。两个实体即便所有属性相同,只要标识不同,就是不同的对象。在限流熔断场景中,RateLimiter 实例、CircuitBreaker 实例都是典型的实体——每个实例拥有唯一的名称或 ID,其状态(如令牌桶余量、熔断开关)随时间推移而改变。

特征 说明 限流熔断示例
唯一标识 通过 ID(如 UUID、业务主键)区分 circuitBreakerId: "cb:payment:svc"
可变状态 属性可被修改,状态随业务操作演进 令牌桶剩余令牌数、熔断器状态(CLOSED/OPEN/HALF_OPEN)
生命周期 从创建到持久化再到销毁,全程可追踪 熔断器从初始化 → 熔断 → 半开 → 恢复
💡 实战提示:在 NestJS 中,实体通常以 TypeORM/Mongoose 的 @Entity() 或 @Schema() 装饰器标记,并配合 @PrimaryGeneratedColumn() 生成唯一标识。限流熔断的实体应放在 domain/entities/ 目录下,与基础设施层解耦。

2. 值对象(Value Object)

值对象通过属性值来定义自身,没有唯一标识,且通常是不可变的。两个值对象如果所有属性相同,则视为相等。在限流熔断领域,RateLimitConfig(包含窗口大小、最大请求数)、CircuitBreakerPolicy(包含失败阈值、超时时间)都是典型的值对象。

  • 不可变性:一旦创建,属性不可修改;需要变更时创建新实例。
  • 无标识:比较基于所有字段的相等性,而非 ID。
  • 自包含行为:可封装校验逻辑,如 isValid()、withIncreasedThreshold()。
// 值对象示例:限流配置
export class RateLimitConfig {
  constructor(
    public readonly windowMs: number,
    public readonly maxRequests: number,
  ) {
    if (windowMs <= 0 || maxRequests <= 0) {
      throw new Error('Invalid rate limit configuration');
    }
  }

  equals(other: RateLimitConfig): boolean {
    return this.windowMs === other.windowMs
        && this.maxRequests === other.maxRequests;
  }
}

3. 聚合(Aggregate)

聚合是一组相关实体和值对象的集群,对外暴露一个聚合根作为唯一访问入口。聚合保证内部数据的一致性,所有外部操作必须通过聚合根进行。在限流熔断场景中,CircuitBreaker 聚合包含:

  • 聚合根:CircuitBreaker 实体(唯一标识 + 状态)
  • 内部实体:FailureRecord(记录每次失败调用)
  • 值对象:CircuitBreakerPolicy(阈值、超时配置)
🎯 聚合边界原则:一次事务只修改一个聚合;聚合之间通过领域事件或仓储接口通信。在 NestJS 中,聚合根通常作为 TypeORM 的 @Entity(),内部实体使用 @OneToMany 关联,但对外只暴露聚合根的方法。
聚合结构:CircuitBreaker 聚合 聚合根:CircuitBreaker id: string | state: enum | lastFailureTime: Date 内部实体:FailureRecord failureId | timestamp | errorCode 值对象:CircuitBreakerPolicy failureThreshold | timeoutMs | halfOpenMaxCalls 外部只能通过聚合根(CircuitBreaker)访问内部实体和值对象 所有状态变更必须经过聚合根的方法,保证一致性 仓储接口:ICircuitBreakerRepository

4. 领域事件(Domain Event)

领域事件是领域中发生的、业务人员关心的、具有业务意义的事件。它记录了“已经发生的事情”,通常以过去时态命名。在限流熔断场景中,关键领域事件包括:

事件名称 触发条件 携带数据 典型订阅者
CircuitBreakerOpened 失败次数超过阈值 breakerId, failureCount, openedAt 告警服务、监控面板
CircuitBreakerHalfOpened 超时时间到达,允许试探 breakerId, halfOpenedAt 负载均衡器、日志记录器
RateLimitExceeded 请求数超过窗口限制 clientId, windowMs, currentCount 限流中间件、客户端通知
⚠️ 领域事件 vs 消息队列:领域事件是业务概念,属于领域层;消息队列是技术实现,属于基础设施层。在 NestJS 中,领域事件先由聚合根发布到内存中的 EventBus,再由应用层决定是否持久化到 RabbitMQ/Kafka。
// 领域事件定义
export class CircuitBreakerOpenedEvent {
  constructor(
    public readonly breakerId: string,
    public readonly failureCount: number,
    public readonly openedAt: Date = new Date(),
  ) {}
}

// 聚合根中发布事件
export class CircuitBreaker {
  private events: DomainEvent[] = [];

  recordFailure(): void {
    this.failureCount++;
    if (this.failureCount >= this.policy.failureThreshold) {
      this.state = CircuitState.OPEN;
      this.events.push(
        new CircuitBreakerOpenedEvent(this.id, this.failureCount)
      );
    }
  }

  pullEvents(): DomainEvent[] {
    const events = [...this.events];
    this.events = [];
    return events;
  }
}

5. 限界上下文(Bounded Context)

限界上下文是 DDD 中最关键的战术模式之一。它定义了模型、术语和业务规则的明确边界。每个限界上下文内部有统一的语言(Ubiquitous Language),上下文之间通过上下文映射(Context Map)进行集成。

在限流熔断课程中,我们识别出三个核心限界上下文:

  • 限流上下文(Rate Limiting Context):负责令牌桶/滑动窗口算法、配额管理、客户端速率控制。术语:令牌、窗口、配额、节流。
  • 熔断上下文(Circuit Breaking Context):负责熔断状态机、失败计数、半开试探、健康检测。术语:熔断器、阈值、半开、恢复。
  • 监控上下文(Monitoring Context):负责事件收集、指标聚合、告警触发、可视化。术语:指标、事件流、告警规则、仪表盘。
限界上下文与上下文映射 限流上下文 RateLimiter TokenBucket SlidingWindow RateLimitConfig 熔断上下文 CircuitBreaker FailureRecord CircuitState HalfOpenTester 监控上下文 EventCollector MetricAggregator AlertEngine Dashboard 上下文映射模式:防腐层(ACL) 限流 → 熔断:通过领域事件解耦;熔断 → 监控:通过事件总线推送 每个上下文内部使用统一的业务语言(Ubiquitous Language) 例如:熔断上下文中“熔断”≠ 限流上下文中“节流”

概念对比速查表

概念 有无唯一标识 是否可变 比较方式 限流熔断示例
实体 有 是 按 ID 比较 CircuitBreaker 实例
值对象 无 否(不可变) 按所有属性比较 RateLimitConfig
聚合 通过聚合根标识 通过聚合根方法变更 按聚合根 ID 比较 CircuitBreaker + FailureRecord + Policy
领域事件 有(事件 ID) 否(已发生) 按事件类型 + 时间戳 CircuitBreakerOpened
限界上下文 有(上下文名称) 边界固定,内部模型可变 按上下文边界划分 限流上下文 vs 熔断上下文
📐 架构决策:在 NestJS 项目中,每个限界上下文对应一个独立的 NestJS 模块(如 RateLimitingModule、CircuitBreakingModule),模块内部按 DDD 分层组织:domain/(实体、值对象、领域事件)、application/(用例服务)、infrastructure/(仓储实现、事件总线适配器)。上下文之间通过 @Module({ imports: [...] }) 或消息队列进行集成,避免直接依赖内部模型。