4. 错误类型定义:系统错误、业务错误、第三方服务错误、自定义错误码体系设计

在 BFF(Backend For Frontend)层构建健壮的错误处理机制,首要任务是建立一套清晰、可扩展的错误类型体系。错误类型的合理划分决定了中间件能否精准捕获、熔断器能否正确触发、客户端能否有效处理。本章将系统定义三大核心错误类别,并设计一套面向生产环境的自定义错误码体系。

4.1 错误分类的三维模型

BFF 层处于前端与后端微服务之间,其错误来源可归纳为三个维度:系统内部、业务逻辑、外部依赖。三者之间的边界决定了错误处理策略的差异。

核心原则:系统错误应快速失败(Fail Fast),业务错误应优雅降级(Graceful Degradation),第三方服务错误应触发熔断(Circuit Breaking)。
BFF 错误分类三维模型 系统错误 内存溢出 空指针异常 数据库连接失败 配置加载异常 业务错误 参数校验失败 用户权限不足 资源状态冲突 业务规则违反 第三方服务错误 上游服务超时 下游服务限流 外部 API 异常 服务不可用 处理策略:快速失败 / 优雅降级 / 熔断保护 错误码映射规则 系统错误:5xx 系列 | 业务错误:4xx 系列 | 第三方服务错误:5xx + 自定义子码 每个错误码必须唯一、可追溯、可自动化处理

4.2 系统错误(System Error)

系统错误指 BFF 层自身运行时发生的不可预期故障,通常由基础设施、运行时环境或代码缺陷引发。这类错误的特点是:不可恢复、不可重试、需要运维介入。

错误类别 典型场景 HTTP 状态码 处理策略
内存/资源溢出 堆内存泄漏、栈溢出、文件句柄耗尽 500 立即终止请求,记录完整堆栈,触发告警
配置异常 配置文件缺失、格式错误、环境变量未设置 500 启动时 Fail-Fast,拒绝提供服务
数据库连接失败 连接池耗尽、数据库宕机、网络分区 503 重试策略 + 降级缓存
序列化/反序列化错误 JSON 解析失败、类型不匹配、字段缺失 500 记录原始数据,返回通用错误响应
注意:系统错误不应暴露内部实现细节给客户端。错误响应中应隐藏堆栈信息,仅返回通用错误码和简要描述。堆栈信息应写入服务端日志,供运维排查使用。

4.3 业务错误(Business Error)

业务错误由前端请求的语义或上下文不满足业务规则而产生。这类错误是可预期的、可恢复的,客户端通常需要根据错误码进行差异化处理。

业务错误的核心特征:

  • 可预知性:业务规则在开发阶段已明确,错误场景可枚举。
  • 可恢复性:客户端修正请求参数或状态后,可重新发起请求。
  • 语义明确:错误信息应包含具体字段、约束条件和修复建议。
// Node.js 业务错误定义示例
class BusinessError extends Error {
  constructor(code, message, details = null) {
    super(message);
    this.name = 'BusinessError';
    this.code = code;          // 业务错误码,如 'USER_NOT_FOUND'
    this.httpStatus = 400;     // 业务错误统一使用 4xx
    this.details = details;    // 附加字段,如 { field: 'email', reason: '格式不正确' }
    this.timestamp = Date.now();
  }
}

// 使用场景
throw new BusinessError(
  'ORDER_STATUS_CONFLICT',
  '订单当前状态不允许取消操作',
  { orderId: 'ORD20241001', currentStatus: 'SHIPPED', expectedStatus: 'PENDING' }
);

4.4 第三方服务错误(Third-Party Service Error)

BFF 层作为聚合网关,频繁调用下游微服务或外部 API。第三方服务错误是 BFF 层最复杂的错误类型,因为它涉及网络通信、服务可用性、超时、限流等多种因素。

错误子类型 触发条件 BFF 处理策略 熔断阈值建议
超时错误 下游响应超过 BFF 设置的超时时间 快速失败 + 返回降级数据 连续 5 次超时触发熔断
限流错误 下游返回 429 Too Many Requests 指数退避重试(最多 3 次) 连续 10 次限流触发熔断
服务不可用 下游返回 503 或连接拒绝 立即熔断,返回缓存或默认值 连续 3 次不可用触发熔断
数据异常 下游返回的数据格式或内容不符合契约 记录异常,返回降级响应 连续 5 次数据异常触发熔断
// Go 第三方服务错误定义示例
type ThirdPartyError struct {
    ServiceName string        // 下游服务名称
    Endpoint    string        // 请求端点
    StatusCode  int           // 下游返回的 HTTP 状态码
    Err         error         // 原始错误
    Retryable   bool          // 是否可重试
    CircuitOpen bool          // 是否已触发熔断
}

func (e *ThirdPartyError) Error() string {
    return fmt.Sprintf("[%s] %s failed with status %d: %v",
        e.ServiceName, e.Endpoint, e.StatusCode, e.Err)
}

// 熔断判断逻辑
func (e *ThirdPartyError) ShouldCircuitBreak() bool {
    return e.StatusCode == 503 || e.StatusCode == 0 || !e.Retryable
}

4.5 自定义错误码体系设计

一套优秀的错误码体系应满足:唯一性、可扩展性、可读性、可自动化处理。推荐采用分层编码结构,将错误码拆分为多个语义段。

自定义错误码分层结构 错误码格式:A-BB-CCCC A: 错误大类 (1位) | BB: 服务模块 (2位) | CCCC: 具体错误 (4位) 1: 系统错误 5xx 映射 2: 业务错误 4xx 映射 3: 第三方错误 5xx + 子码 示例:2-01-0001 → 业务错误(2) - 用户模块(01) - 用户不存在(0001) 示例:3-05-1003 → 第三方错误(3) - 支付服务(05) - 支付超时(1003)

4.5.1 错误码分配表

错误码范围 所属大类 模块标识 说明
1-00-0001 ~ 1-00-0999 系统错误 00(通用) BFF 框架级错误,如配置缺失、内存溢出
1-01-1000 ~ 1-01-1999 系统错误 01(基础设施) 数据库、缓存、消息队列连接异常
2-01-0001 ~ 2-01-0999 业务错误 01(用户模块) 用户不存在、密码错误、账号锁定
2-02-1000 ~ 2-02-1999 业务错误 02(订单模块) 订单状态冲突、库存不足、金额校验失败
3-05-0001 ~ 3-05-0999 第三方错误 05(支付服务) 支付超时、支付拒绝、渠道异常
3-08-1000 ~ 3-08-1999 第三方错误 08(推荐服务) 推荐引擎超时、模型加载失败

4.5.2 错误码注册与维护规范

  1. 集中注册:所有错误码必须在统一的 error-codes.yaml 或 ErrorCode.java 中注册,禁止在代码中硬编码数字。
  2. 版本化管理:错误码变更需经过 Code Review,并更新 API 文档。
  3. 自动化校验:CI 阶段自动检查错误码是否重复、格式是否正确、是否已废弃。
  4. 文档同步:错误码表应自动生成前端可读的文档,便于客户端开发人员查阅。
# error-codes.yaml 示例
version: "1.0.0"
errors:
  # 系统错误
  - code: "1-00-0001"
    message: "内部服务器错误"
    httpStatus: 500
    description: "BFF 层发生未预期的系统异常"
    action: "请联系运维人员查看日志"

  - code: "1-01-1001"
    message: "数据库连接池耗尽"
    httpStatus: 503
    description: "数据库连接池已达到最大连接数"
    action: "请稍后重试"

  # 业务错误
  - code: "2-01-0001"
    message: "用户不存在"
    httpStatus: 404
    description: "请求的用户 ID 在系统中不存在"
    action: "请检查用户 ID 是否正确"

  - code: "2-02-1001"
    message: "订单状态不允许取消"
    httpStatus: 409
    description: "当前订单状态已超出可取消的时间窗口"
    action: "请联系客服处理"

  # 第三方服务错误
  - code: "3-05-0001"
    message: "支付服务超时"
    httpStatus: 504
    description: "调用支付服务超过 3 秒未响应"
    action: "请稍后重试或选择其他支付方式"
    circuitBreak: true
    retryable: true
最佳实践:错误码的 message 字段面向开发者,应保持技术准确;action 字段面向最终用户,应提供可操作的修复建议。BFF 层在返回错误响应时,可根据客户端类型(Web / App / 第三方)选择性地暴露 action 字段。

4.6 错误类型与中间件穿透策略

错误类型定义直接影响中间件的处理流程。不同类型的错误在中间件链中的穿透行为应有所区别:

错误类型 中间件穿透行为 是否触发熔断 是否记录日志 是否返回给客户端
系统错误 立即中断,不继续穿透 否 是(全量堆栈) 仅返回通用错误码
业务错误 穿透至业务中间件处理 否 是(业务上下文) 返回完整错误信息
第三方服务错误 穿透至熔断中间件 是 是(服务名+耗时) 返回降级响应

通过上述分类与编码体系,BFF 层能够实现:

  • 精确的异常定位:从错误码即可判断错误来源、所属模块、具体原因。
  • 差异化的处理策略:系统错误快速失败,业务错误优雅提示,第三方错误触发熔断。
  • 可观测性增强:错误码与监控指标、告警规则直接关联,提升运维效率。
  • 前后端协作效率:前端根据错误码做差异化 UI 展示,无需解析模糊的文本信息。