3. 领域驱动设计基础:DDD核心概念与战略/战术设计

领域驱动设计(Domain-Driven Design,DDD)是一套应对复杂业务系统的建模与设计方法论。在GoBFF架构中,DDD帮助我们精确划分业务接口的领域边界,避免业务逻辑泄漏到基础设施层。本节将系统讲解DDD的核心概念、战略设计与战术设计,为后续的BFF领域建模打下理论基础。

3.1 DDD核心概念

DDD的核心概念分为两类:战术设计中的建模元素(实体、值对象、聚合、领域服务)和战略设计中的边界划分工具(限界上下文、上下文映射)。我们先从战术设计的基础元素开始。

3.1.1 实体(Entity)

实体是具有唯一标识且生命周期持续变化的对象。在GoBFF中,实体通常对应业务中的核心概念,如用户、订单、商品。

  • 唯一标识:每个实体通过ID(如UUID、自增ID)区分,即使属性完全相同,不同ID代表不同实体。
  • 可变性:实体的属性可以随时间变化,但标识保持不变。
  • 业务行为:实体封装与其身份相关的业务逻辑,而非仅作为数据容器。
Go示例:实体通常包含业务方法,而非仅暴露getter/setter。
// Order 是典型的实体,具有唯一标识和业务行为
type Order struct {
    ID         string    // 唯一标识
    CustomerID string
    Items      []OrderItem
    Status     OrderStatus
    CreatedAt  time.Time
}

// 实体封装业务行为
func (o *Order) Cancel() error {
    if o.Status == OrderStatusShipped {
        return ErrCannotCancelShippedOrder
    }
    o.Status = OrderStatusCancelled
    return nil
}

3.1.2 值对象(Value Object)

值对象通过属性值来定义,没有唯一标识,且通常是不可变的。在GoBFF中,值对象用于描述实体的属性或业务概念,如地址、金额、时间段。

  • 无标识:两个值对象如果所有属性相同,则视为相等。
  • 不可变性:值对象创建后不应修改,修改应返回新实例。
  • 自包含:值对象可以包含验证逻辑和业务方法。
// Money 是值对象,不可变且通过属性定义相等性
type Money struct {
    amount   decimal.Decimal
    currency string
}

func NewMoney(amount decimal.Decimal, currency string) (Money, error) {
    if amount.IsNegative() {
        return Money{}, ErrNegativeAmount
    }
    if currency == "" {
        return Money{}, ErrEmptyCurrency
    }
    return Money{amount: amount, currency: currency}, nil
}

// 值对象方法返回新实例,而非修改自身
func (m Money) Add(other Money) (Money, error) {
    if m.currency != other.currency {
        return Money{}, ErrCurrencyMismatch
    }
    return NewMoney(m.amount.Add(other.amount), m.currency)
}
特性 实体(Entity) 值对象(Value Object)
唯一标识 有(ID) 无
可变性 可变 不可变
相等性判断 通过ID 通过所有属性
生命周期 持续变化 无独立生命周期
典型例子 用户、订单、商品 金额、地址、时间段

3.1.3 聚合(Aggregate)

聚合是一组相关实体和值对象的集合,通过聚合根(Aggregate Root)作为外部访问的唯一入口。聚合保证了内部数据的一致性和不变性。

  • 聚合根:聚合中唯一的实体,外部只能通过聚合根操作聚合内的对象。
  • 一致性边界:聚合内的所有修改必须满足业务不变量,聚合根负责校验。
  • 原子性:每次操作聚合时,整个聚合作为一个整体持久化。
// Order 作为聚合根,管理内部的 OrderItem 实体和 Money 值对象
type Order struct {
    ID         string
    CustomerID string
    Items      []OrderItem  // 聚合内的实体
    Total      Money        // 聚合内的值对象
    Status     OrderStatus
}

// 外部只能通过聚合根操作
func (o *Order) AddItem(productID string, quantity int, price Money) error {
    if o.Status != OrderStatusPending {
        return ErrCannotModifyNonPendingOrder
    }
    item := NewOrderItem(productID, quantity, price)
    o.Items = append(o.Items, item)
    o.Total = o.calculateTotal() // 重新计算总额,保证一致性
    return nil
}

// 聚合内的实体,外部不可直接访问
type OrderItem struct {
    ProductID string
    Quantity  int
    Price     Money
}
设计原则:聚合应尽量小,只包含必须保持一致的实体和值对象。过大的聚合会导致性能问题和并发冲突。

3.1.4 领域服务(Domain Service)

当某个业务操作不属于任何实体或值对象,或者涉及多个聚合的协作时,应将其建模为领域服务。领域服务是无状态的,只包含业务逻辑。

  • 无状态:领域服务不持有自身状态,所有状态通过参数传入。
  • 业务操作:封装跨聚合的业务流程或计算。
  • 命名规范:通常以动词命名,如 TransferService、PaymentService。
// TransferService 是领域服务,处理跨聚合的转账操作
type TransferService struct {
    accountRepo AccountRepository
}

func (s *TransferService) Transfer(fromID, toID string, amount Money) error {
    from := s.accountRepo.FindByID(fromID)
    to := s.accountRepo.FindByID(toID)

    // 领域服务协调多个聚合
    if err := from.Withdraw(amount); err != nil {
        return err
    }
    if err := to.Deposit(amount); err != nil {
        // 回滚操作,保证一致性
        from.Deposit(amount) // 补偿操作
        return err
    }
    return nil
}

3.2 DDD战略设计

战略设计关注宏观的领域划分和上下文边界,帮助团队在大型系统中确定每个子领域的职责和交互方式。

3.2.1 限界上下文(Bounded Context)

限界上下文是DDD战略设计的核心,它定义了模型应用的边界。在每个限界上下文内部,模型是统一且自洽的;不同上下文之间通过上下文映射进行集成。

  • 明确边界:每个上下文有独立的领域模型、数据库、团队和部署单元。
  • 统一语言:团队在上下文内使用相同的业务术语,避免歧义。
  • 独立演化:上下文之间通过接口通信,内部实现可以独立变化。
常见误区:不要试图创建一个“通用”的领域模型覆盖所有上下文。不同上下文对同一概念的理解可能不同(例如“用户”在订单上下文和营销上下文中含义不同)。

3.2.2 上下文映射(Context Map)

上下文映射描述了限界上下文之间的协作关系。常见的映射模式包括:

模式 描述 适用场景
合作关系(Partnership) 两个团队协同演进,共同维护集成接口 紧密协作的子系统
共享内核(Shared Kernel) 共享部分领域模型,保持同步 高度依赖的核心模块
客户-供应商(Customer-Supplier) 上游团队提供接口,下游团队按需消费 API提供方与消费方
防腐层(Anti-Corruption Layer) 在下游上下文建立转换层,隔离上游模型变化 集成遗留系统或第三方服务
开放主机服务(Open Host Service) 上游以协议形式公开服务,下游通过协议集成 微服务间的REST/gRPC通信
限界上下文与上下文映射示例 订单上下文 Order OrderItem Payment 库存上下文 Product Warehouse StockLevel 用户上下文 User Address Profile 开放主机服务 (REST API) 客户-供应商 (gRPC) 上下文映射说明 • 订单上下文通过开放主机服务(REST API)调用库存上下文,查询库存可用性。 • 订单上下文作为客户,通过gRPC调用用户上下文获取用户地址信息。 • 每个上下文拥有独立的领域模型和数据库,模型之间不直接共享。 • 上下文映射帮助团队明确集成方式,避免紧耦合。

3.3 DDD战术设计

战术设计关注微观的模型实现,将战略设计中的限界上下文细化为具体的代码结构。在GoBFF中,战术设计指导我们如何组织包结构、定义领域对象和实现业务逻辑。

3.3.1 分层架构

DDD推荐使用分层架构来隔离关注点,典型的分层包括:

  • 用户接口层(Interface):处理HTTP请求、gRPC调用、消息队列等外部输入。
  • 应用层(Application):编排领域对象,处理事务和安全校验。
  • 领域层(Domain):核心业务逻辑,包含实体、值对象、聚合、领域服务。
  • 基础设施层(Infrastructure):提供持久化、消息、缓存等技术实现。
依赖原则:领域层不依赖任何其他层;应用层依赖领域层;基础设施层依赖领域层接口。

3.3.2 仓储模式(Repository)

仓储为聚合提供集合式的访问接口,隐藏底层持久化细节。在GoBFF中,仓储通常定义为接口,由基础设施层实现。

// 仓储接口定义在领域层
type OrderRepository interface {
    Save(ctx context.Context, order *Order) error
    FindByID(ctx context.Context, id string) (*Order, error)
    FindByCustomerID(ctx context.Context, customerID string) ([]*Order, error)
    Delete(ctx context.Context, id string) error
}

// 基础设施层实现仓储接口
type PostgresOrderRepository struct {
    db *sql.DB
}

func (r *PostgresOrderRepository) Save(ctx context.Context, order *Order) error {
    // 实现持久化逻辑
    tx, err := r.db.BeginTx(ctx, nil)
    if err != nil {
        return err
    }
    // 保存聚合根和内部对象
    // ...
    return tx.Commit()
}

3.3.3 领域事件(Domain Event)

领域事件记录领域中发生的业务事件,用于解耦聚合之间的通信。在GoBFF中,领域事件常用于跨上下文的异步通知。

// 领域事件定义
type OrderCreatedEvent struct {
    OrderID    string
    CustomerID string
    Total      Money
    CreatedAt  time.Time
}

// 聚合根发布事件
func (o *Order) Create() error {
    if o.Status != OrderStatusPending {
        return ErrInvalidStatus
    }
    o.Status = OrderStatusCreated
    o.AddEvent(OrderCreatedEvent{
        OrderID:    o.ID,
        CustomerID: o.CustomerID,
        Total:      o.Total,
        CreatedAt:  time.Now(),
    })
    return nil
}

3.4 战略设计与战术设计的关系

战略设计和战术设计是DDD的两个互补维度,共同构成完整的领域建模体系。

维度 关注点 产出物 在GoBFF中的应用
战略设计 宏观边界、团队协作、系统集成 限界上下文、上下文映射、统一语言 划分BFF与后端服务的边界,确定接口协议
战术设计 微观模型、代码实现、业务逻辑 实体、值对象、聚合、领域服务、仓储 实现BFF中的业务接口逻辑,封装领域规则
战略设计与战术设计的关系 战略设计层 限界上下文 Bounded Context 上下文映射 Context Map 统一语言 Ubiquitous Language 指导 战术设计层 实体 Entity 值对象 Value Object 聚合 Aggregate 领域服务 Domain Service 仓储 Repo 战略设计确定“在哪里做”,战术设计确定“怎么做”。两者结合才能构建健壮的领域模型。

3.5 本章小结

DDD的核心概念为GoBFF业务接口的领域边界设计提供了坚实的理论基础:

  • 实体和值对象是领域模型的基本构建块,分别处理有标识和无需标识的业务概念。
  • 聚合通过聚合根保证数据一致性和业务不变量,是事务操作的基本单元。
  • 领域服务处理跨聚合的业务逻辑,保持领域层的纯净。
  • 战略设计通过限界上下文和上下文映射划分系统边界,指导团队协作和系统集成。
  • 战术设计通过分层架构、仓储模式和领域事件,将战略设计落地为可执行的代码结构。
实践建议:在GoBFF项目中,建议先进行战略设计,明确BFF与后端服务、前端应用之间的限界上下文;再在每个上下文内进行战术设计,使用实体、值对象、聚合等元素构建领域模型。后续章节将基于这些概念,深入讲解如何在GoBFF中实现具体的业务接口领域边界。