4. BFF 错误对象标准化设计:统一错误响应结构、错误码枚举体系与国际化错误消息
在 BFF 架构中,错误处理的首要任务是建立一套标准化错误对象。没有标准化的错误响应,前端无法统一处理异常、监控系统无法聚合告警、排查人员无法快速定位根因。本章将深入讲解 BFF 错误对象的四要素结构、错误码枚举体系的设计原则以及国际化错误消息的实现方案。
核心原则:BFF 返回给客户端的每一个错误响应,都必须遵循相同的 JSON 结构,携带可枚举的错误码、人类可读的消息、可追踪的 traceId,以及可选的业务数据。
4.1 统一错误响应结构:四要素模型
我们定义 BFF 错误响应的标准 JSON 结构,包含四个核心字段:code、message、data、traceId。这个结构同时覆盖成功响应和错误响应,前端只需解析 code 字段即可判断请求状态。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| code | string | 是 | 业务错误码,如 SUCCESS、PARAM_INVALID、USER_NOT_FOUND |
| message | string | 是 | 人类可读的错误描述,支持国际化 |
| data | object/null | 是 | 成功时返回业务数据,失败时通常为 null 或包含额外错误详情 |
| traceId | string | 是 | 全链路追踪 ID,用于关联日志和排查问题 |
以下是一个标准的错误响应示例:
{
"code": "ORDER_PAY_TIMEOUT",
"message": "订单支付超时,请重新提交",
"data": {
"orderId": "20250315001",
"expiredAt": "2025-03-15T10:30:00Z"
},
"traceId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
设计要点:即使请求成功,也返回
code: "SUCCESS" 和 traceId。这保证了前端拦截器可以统一处理所有响应,无需区分成功/失败分支。
4.2 错误码枚举体系:分层与分类
错误码是错误对象的灵魂。一个设计良好的错误码体系需要满足可枚举、可扩展、可归类三个特性。我们采用分层分类的编码方式,将错误码分为系统级、业务级和基础设施级三大类。
下图展示了 BFF 错误码的分层结构:
在实际项目中,我们建议将错误码定义为枚举常量,避免魔法字符串。以下是 Node.js 和 Go 两种语言的实现示例:
// Node.js / TypeScript 错误码枚举
export enum BffErrorCode {
// 系统级
INTERNAL_ERROR = 'SYS_INTERNAL_ERROR',
SERVICE_UNAVAILABLE = 'SYS_SERVICE_UNAVAILABLE',
TIMEOUT = 'SYS_TIMEOUT',
RATE_LIMIT_EXCEEDED = 'SYS_RATE_LIMIT_EXCEEDED',
// 业务级
PARAM_INVALID = 'BIZ_PARAM_INVALID',
USER_NOT_FOUND = 'BIZ_USER_NOT_FOUND',
ORDER_PAY_TIMEOUT = 'BIZ_ORDER_PAY_TIMEOUT',
INSUFFICIENT_BALANCE = 'BIZ_INSUFFICIENT_BALANCE',
// 基础设施级
DB_CONNECTION_LOST = 'INFRA_DB_CONNECTION_LOST',
CACHE_MISS = 'INFRA_CACHE_MISS',
MESSAGE_QUEUE_DOWN = 'INFRA_MESSAGE_QUEUE_DOWN',
THIRD_PARTY_FAILURE = 'INFRA_THIRD_PARTY_FAILURE',
}
// Go 错误码常量
const (
// 系统级
ErrCodeInternalError = "SYS_INTERNAL_ERROR"
ErrCodeServiceUnavailable = "SYS_SERVICE_UNAVAILABLE"
ErrCodeTimeout = "SYS_TIMEOUT"
ErrCodeRateLimitExceeded = "SYS_RATE_LIMIT_EXCEEDED"
// 业务级
ErrCodeParamInvalid = "BIZ_PARAM_INVALID"
ErrCodeUserNotFound = "BIZ_USER_NOT_FOUND"
ErrCodeOrderPayTimeout = "BIZ_ORDER_PAY_TIMEOUT"
ErrCodeInsufficientBalance = "BIZ_INSUFFICIENT_BALANCE"
// 基础设施级
ErrCodeDbConnectionLost = "INFRA_DB_CONNECTION_LOST"
ErrCodeCacheMiss = "INFRA_CACHE_MISS"
ErrCodeMessageQueueDown = "INFRA_MESSAGE_QUEUE_DOWN"
ErrCodeThirdPartyFailure = "INFRA_THIRD_PARTY_FAILURE"
)
4.3 国际化错误消息:多语言支持
BFF 服务可能面向全球用户,错误消息必须支持国际化。我们采用键值对 + 语言环境的方案,将错误码映射到不同语言的消息模板。
国际化消息的设计要点:
- 消息模板化:消息中可包含占位符,如
{paramName}、{userId},运行时动态替换。 - 语言回退:当请求的语言不存在时,默认回退到英文或中文。
- 前端传递语言偏好:通过 HTTP 请求头
Accept-Language或自定义头X-Locale传递。
以下是一个国际化消息配置文件的示例(JSON 格式):
{
"en": {
"BIZ_PARAM_INVALID": "Invalid parameter: {paramName}",
"BIZ_USER_NOT_FOUND": "User not found with id: {userId}",
"BIZ_ORDER_PAY_TIMEOUT": "Order payment timeout, please resubmit",
"SYS_INTERNAL_ERROR": "Internal server error, please try again later"
},
"zh-CN": {
"BIZ_PARAM_INVALID": "参数无效:{paramName}",
"BIZ_USER_NOT_FOUND": "未找到用户,ID:{userId}",
"BIZ_ORDER_PAY_TIMEOUT": "订单支付超时,请重新提交",
"SYS_INTERNAL_ERROR": "服务器内部错误,请稍后重试"
},
"ja": {
"BIZ_PARAM_INVALID": "パラメータが無効です:{paramName}",
"BIZ_USER_NOT_FOUND": "ユーザーが見つかりません:{userId}",
"BIZ_ORDER_PAY_TIMEOUT": "注文の支払いがタイムアウトしました。再送信してください",
"SYS_INTERNAL_ERROR": "サーバー内部エラーが発生しました。後でもう一度お試しください"
}
}
在 BFF 层,我们通过一个 I18nService 来解析语言并填充消息:
// Node.js 国际化消息服务示例
class I18nService {
private messages: Record<string, Record<string, string>>;
constructor(messages: Record<string, Record<string, string>>) {
this.messages = messages;
}
getMessage(code: string, locale: string, params?: Record<string, string>): string {
const langMessages = this.messages[locale] || this.messages['en'];
let template = langMessages[code] || code;
if (params) {
for (const [key, value] of Object.entries(params)) {
template = template.replace(`{${key}}`, value);
}
}
return template;
}
}
避坑指南:不要在 BFF 层做完整的翻译引擎,那会引入不必要的复杂度。BFF 只负责根据语言偏好选择预定义的消息模板并填充参数。完整的翻译管理应交给专门的国际化平台或配置文件。
4.4 错误对象生成流程:从异常到标准化响应
下图展示了 BFF 中一个请求从抛出异常到返回标准化错误对象的完整流程:
流程中的关键步骤:
- 请求进入 BFF 中间件:中间件解析
Accept-Language头,生成traceId,并注入请求上下文。 - 业务逻辑执行:调用下游服务或本地计算,可能抛出业务异常或系统异常。
- 异常捕获与转换:全局错误处理中间件捕获所有异常,根据异常类型映射到对应的错误码。
- 构建标准化响应:调用
I18nService获取国际化消息,组装{ code, message, data, traceId }对象返回。
4.5 最佳实践与常见陷阱
| 实践 | 说明 |
|---|---|
| 错误码必须唯一且稳定 | 一旦发布,错误码不应被删除或修改含义,只能废弃(deprecated)。前端和监控系统可能硬编码了错误码。 |
| 区分客户端错误和服务端错误 | 4xx 错误(如参数校验失败)不应记录为系统告警;5xx 错误(如数据库连接丢失)必须触发告警。 |
| 不要在 message 中暴露敏感信息 | 如数据库表名、堆栈信息、内部 IP。敏感信息应记录在日志中,而非返回给客户端。 |
| traceId 必须贯穿全链路 | 从 BFF 入口生成,传递给下游服务,并在日志中输出。确保错误排查时可以串联所有调用链。 |
| 国际化消息不要硬编码在代码中 | 使用外部配置文件或配置中心管理,支持热更新,避免因修改消息而重新部署服务。 |
推荐做法:在 BFF 的全局错误处理中间件中,统一捕获
BffError 类型(自定义错误类),其他未知异常统一映射为 SYS_INTERNAL_ERROR。这样可以保证所有错误响应都经过标准化流程,不会遗漏。
通过本章的设计,BFF 服务将具备统一的错误响应结构、清晰的错误码枚举体系以及灵活的国际化消息能力。这为后续的故障排查、监控告警和前端统一处理奠定了坚实的基础。