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 中最核心的战略概念。它定义了一个显式的边界,在这个边界内部,领域模型、术语、业务规则都是统一且自洽的;边界之外,模型可以有不同的含义甚至冲突。
关键理解: 同一个业务概念在不同的限界上下文中可能有完全不同的含义。例如,“用户”在“订单上下文”中是一个包含地址和支付信息的实体,而在“营销上下文”中可能只是一个包含标签和偏好的数据对象。
限界上下文的划分遵循以下原则:
- 语言一致性: 一个上下文内部使用统一的通用语言(Ubiquitous Language)。
- 模型独立性: 每个上下文拥有独立的领域模型,不与其他上下文共享数据库或实体类。
- 通信显式化: 上下文之间通过定义好的接口(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 作为领域边界在微服务架构中的位置:
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))
})
}