2. 领域边界定义:DDD、限界上下文与 BFF

在构建微服务架构时,最常遇到的挑战不是技术选型,而是领域边界的划分。边界划得模糊,服务就会变成“分布式大泥球”;边界划得清晰,每个服务才能独立演进。本章从领域驱动设计(DDD)的核心概念出发,逐步推导出 BFF 作为领域边界的必然性。

2.1 领域驱动设计(DDD)简介

领域驱动设计由 Eric Evans 在 2004 年提出,其核心思想是:软件模型必须与业务领域模型对齐。DDD 提供了一套战术和战略工具,帮助团队在复杂业务中建立可维护的软件模型。

核心观点: DDD 不是架构模式,而是一种建模思维方式。它强调技术团队必须与领域专家使用统一的语言(Ubiquitous Language)进行沟通,并将这种语言直接体现在代码中。

DDD 的战略设计部分主要解决两个问题:

  • 如何将大系统拆分为有边界的子领域 —— 通过限界上下文(Bounded Context)实现。
  • 如何让这些子领域之间保持低耦合 —— 通过上下文映射(Context Map)定义集成关系。

下表对比了 DDD 战略设计与战术设计的关注点:

维度 战略设计(Strategic Design) 战术设计(Tactical Design)
关注范围 系统级、组织级 模块级、代码级
核心概念 限界上下文、上下文映射、子域 实体、值对象、聚合、领域服务、领域事件
解决的核心问题 如何划分服务边界 如何在边界内建模业务逻辑
与 BFF 的关系 直接决定 BFF 的粒度与职责 指导 BFF 内部代码的组织方式

2.2 限界上下文(Bounded Context)

限界上下文是 DDD 中最核心的战略概念。它定义了一个显式的边界,在这个边界内部,领域模型、术语、业务规则都是统一且自洽的;边界之外,模型可以有不同的含义甚至冲突。

关键理解: 同一个业务概念在不同的限界上下文中可能有完全不同的含义。例如,“用户”在“订单上下文”中是一个包含地址和支付信息的实体,而在“营销上下文”中可能只是一个包含标签和偏好的数据对象。

限界上下文的划分遵循以下原则:

  1. 语言一致性: 一个上下文内部使用统一的通用语言(Ubiquitous Language)。
  2. 模型独立性: 每个上下文拥有独立的领域模型,不与其他上下文共享数据库或实体类。
  3. 通信显式化: 上下文之间通过定义好的接口(API、事件、消息)进行通信,避免隐式依赖。

下图展示了三个典型限界上下文之间的关系:

限界上下文关系示例 订单上下文 实体:订单、订单项 值对象:金额、地址 领域服务:订单校验 语言:订单、支付、物流 库存上下文 实体:仓库、库存记录 值对象:SKU、批次号 领域服务:库存预留 语言:入库、出库、盘点 支付上下文 实体:交易、退款单 值对象:支付凭证 领域服务:支付网关 语言:交易、退款、对账 每个限界上下文拥有独立的领域模型、数据库和术语体系,通过 API 或事件进行集成

2.3 BFF 作为领域边界

在微服务架构中,BFF(Backend For Frontend)通常被视为一个轻量级的 API 网关层。但从 DDD 的视角来看,BFF 完全可以承担限界上下文边界的角色。这意味着 BFF 不仅仅是请求的转发者,更是领域边界的守护者。

BFF 作为领域边界,需要承担以下职责:

职责 说明 DDD 对应概念
协议转换 将前端(Web/移动端)的请求协议转换为后端微服务所需的协议 上下文映射(Context Map)
数据聚合 从多个后端服务获取数据,组装成前端需要的视图模型 应用服务(Application Service)
权限校验 在边界处统一执行身份认证和授权检查,防止未授权请求进入内部服务 领域边界守卫
模型隔离 将后端领域模型转换为前端展示模型,避免领域模型泄露到客户端 防腐层(Anti-Corruption Layer)
常见误区: 很多团队将 BFF 当作“万能代理层”,在里面塞入大量业务逻辑,导致 BFF 变成“大泥球”。正确的做法是:BFF 只做编排(Orchestration),不做业务决策。业务逻辑应该下沉到对应的领域服务中。

下图展示了 BFF 作为领域边界在微服务架构中的位置:

BFF 作为领域边界的架构位置 前端客户端(Web / iOS / Android) 每个前端类型拥有独立的 BFF 实例 BFF 领域边界 职责:协议转换 · 数据聚合 · 权限校验 · 模型隔离 技术栈:Go HTTP 服务 + JWT 中间件 + 领域客户端 订单服务 限界上下文:订单 数据库:订单库 API:gRPC / REST 用户服务 限界上下文:用户 数据库:用户库 API:gRPC / REST 支付服务 限界上下文:支付 数据库:支付库 API:gRPC / REST BFF 位于前端与后端微服务之间,作为显式的领域边界,隔离前端与后端的领域模型

2.4 为什么 BFF 适合作为权限边界

权限验证是 BFF 作为领域边界最典型的应用场景。将权限校验放在 BFF 层,而不是在每个后端微服务中重复实现,具有以下优势:

  • 集中管控: 所有前端请求在进入内部网络之前,统一经过 BFF 的权限校验,避免权限逻辑分散在多个服务中。
  • 模型简化: 后端微服务可以专注于业务逻辑,无需关心“谁在请求”以及“是否有权限”,这些信息由 BFF 在请求中注入。
  • 策略灵活: 不同的前端类型(Web、移动端、第三方)可以在各自的 BFF 中实现不同的权限策略,互不影响。
实战建议: 在 Go 中实现 BFF 权限验证时,推荐使用中间件模式。在 HTTP 路由层注册一个全局权限中间件,该中间件负责解析 JWT Token、提取用户角色和权限列表,然后通过 Context 传递给下游的 Handler。Handler 只需从 Context 中读取权限信息,无需重复解析 Token。

以下是一个 Go 语言中 BFF 权限中间件的核心代码骨架:

// 权限中间件示例
func AuthMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 1. 从请求头中提取 JWT Token
        tokenStr := r.Header.Get("Authorization")
        if tokenStr == "" {
            http.Error(w, "missing token", http.StatusUnauthorized)
            return
        }

        // 2. 解析 Token,提取用户信息和权限列表
        claims, err := parseJWT(tokenStr)
        if err != nil {
            http.Error(w, "invalid token", http.StatusUnauthorized)
            return
        }

        // 3. 将用户信息注入到请求上下文中
        ctx := context.WithValue(r.Context(), "user", claims.User)
        ctx = context.WithValue(ctx, "permissions", claims.Permissions)

        // 4. 继续执行后续 Handler
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}