4. 业务接口领域边界定义

在 GoBFF 架构中,领域边界是划分业务能力与数据职责的核心分界线。没有清晰的边界,BFF 层会退化为“万能胶水层”,导致接口膨胀、逻辑混乱、维护成本指数级上升。本章将系统讲解领域边界的定义、识别方法以及划分原则。

4.1 什么是领域边界

领域边界(Domain Boundary)是指在一个业务系统中,不同业务能力域之间的逻辑与数据隔离线。在 BFF 场景下,领域边界决定了:

  • 接口归属:某个 API 端点应该由哪个领域服务提供
  • 数据所有权:某个业务实体(如订单、用户、商品)的读写权限属于哪个领域
  • 通信协议:领域之间通过什么方式交互(同步 RPC、异步消息、事件驱动)
  • 变更影响范围:修改一个领域的内部逻辑,不会破坏其他领域的接口契约
核心认知:领域边界不是技术架构的产物,而是业务概念的映射。BFF 的领域边界应当反映真实世界的业务划分,而非数据库表结构或微服务部署单元。

4.2 如何识别业务领域

识别业务领域是领域驱动设计(DDD)中的核心活动。在 GoBFF 实践中,我们推荐使用“事件风暴 + 名词动词分析法”的组合策略:

4.2.1 事件风暴法(Event Storming)

通过组织业务专家与开发团队共同参与的工作坊,识别出业务过程中的关键事件、命令、聚合和限界上下文。步骤如下:

  1. 列出业务事件:如“订单已创建”、“支付已确认”、“库存已扣减”
  2. 找出触发命令:如“创建订单”、“确认支付”、“扣减库存”
  3. 识别聚合根:每个命令操作的核心实体,如“订单”、“支付单”、“库存记录”
  4. 划分限界上下文:将紧密关联的聚合和事件归为一组,形成领域边界

4.2.2 名词动词分析法

从业务需求文档或用户故事中提取核心名词和动词,通过聚类形成候选领域:

业务名词 相关动词 候选领域
用户、账户、登录凭证 注册、登录、修改资料、重置密码 用户领域(User Domain)
商品、SKU、分类、库存 上架、下架、查询、调价 商品领域(Product Domain)
订单、订单项、物流单 创建、支付、发货、取消、退款 订单领域(Order Domain)
支付单、交易流水、退款单 支付、回调、退款、对账 支付领域(Payment Domain)
实战建议:在 GoBFF 项目中,每个领域对应一个独立的 Go package(如 internal/domain/user、internal/domain/order),包内包含实体、值对象、领域服务和仓储接口。领域之间通过接口依赖,而非直接结构体引用。

4.3 边界划分原则:高内聚低耦合

“高内聚、低耦合”是软件工程的基本原则,在领域边界划分中具体表现为以下四个子原则:

4.3.1 单一职责原则(SRP)

一个领域只负责一个业务能力域。例如,订单领域不应包含用户密码校验逻辑,支付领域不应直接操作商品库存。

4.3.2 变更隔离原则

当业务需求变化时,影响范围应局限在单个领域内。如果修改“订单状态流转”需要同时修改“支付领域”和“物流领域”的代码,说明边界划分不合理。

4.3.3 数据所有权原则

每个业务实体只能有一个领域拥有写权限,其他领域只能通过接口读取。例如,“订单金额”只能由订单领域修改,支付领域只能读取。

4.3.4 通信显式化原则

领域之间的交互必须通过显式的接口或事件进行,禁止隐式共享数据库表或全局变量。在 Go 中,这体现为 interface 定义和事件总线。

领域边界划分原则示意图 用户领域 注册 / 登录 资料管理 权限校验 用户实体 订单领域 创建订单 状态流转 订单查询 订单实体 支付领域 支付处理 退款处理 对账服务 支付单实体 高内聚 每个领域内部的功能紧密相关 修改用户资料不影响订单逻辑 领域内方法可复用、可测试 低耦合 领域之间通过接口/事件通信 不共享数据库表或全局变量 一个领域宕机不影响其他领域 边界划分结果:每个领域独立演进,通过 BFF 编排层组合能力 用户领域 → 订单领域 → 支付领域:单向依赖,职责清晰

4.4 边界划分的常见误区

误区 表现 后果 正确做法
按数据库表划分 每个表对应一个领域 领域粒度太细,接口碎片化 按业务能力聚合表
按微服务划分 一个微服务就是一个领域 过度拆分,BFF 编排复杂 领域是逻辑概念,可多个领域部署在一个服务
按前端页面划分 每个页面一个领域 领域与 UI 强耦合,复用性差 领域反映业务能力,而非页面结构
忽略上下文映射 领域之间直接调用内部方法 循环依赖,变更连锁反应 使用防腐层(ACL)或事件

4.5 GoBFF 中的边界实现示例

以下代码展示如何在 Go 中通过 interface 定义领域边界:

// internal/domain/order/repository.go
package order

// OrderRepository 定义订单仓储接口,属于订单领域边界
type OrderRepository interface {
    Save(ctx context.Context, order *Order) error
    FindByID(ctx context.Context, orderID string) (*Order, error)
    UpdateStatus(ctx context.Context, orderID string, status OrderStatus) error
}

// internal/domain/payment/service.go
package payment

// PaymentService 支付领域服务,只依赖接口,不依赖具体实现
type PaymentService interface {
    ProcessPayment(ctx context.Context, req PaymentRequest) (*PaymentResult, error)
    Refund(ctx context.Context, paymentID string, amount float64) error
}

// internal/bff/order/handler.go
package orderhandler

// OrderBFFHandler BFF 层编排多个领域
type OrderBFFHandler struct {
    orderRepo   order.OrderRepository   // 依赖接口
    paymentSvc  payment.PaymentService  // 依赖接口
    userSvc     user.UserService        // 依赖接口
}

func (h *OrderBFFHandler) CreateOrder(ctx context.Context, req CreateOrderRequest) (*OrderResponse, error) {
    // 1. 校验用户(调用用户领域接口)
    user, err := h.userSvc.GetUser(ctx, req.UserID)
    if err != nil {
        return nil, fmt.Errorf("get user: %w", err)
    }
    // 2. 创建订单(调用订单领域接口)
    order, err := h.orderRepo.Save(ctx, &order.Order{UserID: user.ID, ...})
    if err != nil {
        return nil, fmt.Errorf("save order: %w", err)
    }
    // 3. 发起支付(调用支付领域接口)
    result, err := h.paymentSvc.ProcessPayment(ctx, payment.PaymentRequest{
        OrderID: order.ID,
        Amount:  order.TotalAmount,
    })
    if err != nil {
        return nil, fmt.Errorf("process payment: %w", err)
    }
    return &OrderResponse{OrderID: order.ID, PaymentID: result.PaymentID}, nil
}
边界红线:BFF 层只能编排领域接口,不能绕过接口直接操作领域内部的数据结构或数据库。如果 BFF 层出现了 order.Status = "paid" 这样的赋值语句,说明领域边界已被破坏。

4.6 边界验证清单

完成领域边界划分后,使用以下清单进行自检:

  • ✅ 单一职责:每个领域是否只描述一个业务能力?
  • ✅ 独立演进:修改一个领域的内部实现,是否需要修改其他领域的代码?
  • ✅ 数据主权:每个业务实体是否只有一个写所有者?
  • ✅ 接口契约:领域之间的交互是否全部通过 interface 定义?
  • ✅ 测试隔离:能否独立测试每个领域,而不依赖其他领域的真实实现?
  • ✅ 部署独立:如果未来需要将某个领域拆为独立服务,改动成本是否可控?

通过以上步骤和原则,你可以在 GoBFF 项目中建立清晰、稳定、可演进的领域边界,为后续的接口设计和编排打下坚实基础。