第1章:Result封装的核心职责
跨模块调用时,错误信息的传递方式决定了系统的可观测性与恢复能力。Result 类型的封装并非简单的“包装返回值”,而是在模块边界处建立一组契约,使调用方能够以统一模式处理成功与失败路径。
1.1 统一返回值结构
每个模块对外暴露的函数都返回相同的容器类型,而非裸值或异常。该容器固定包含:状态码(code)、消息描述(message)、有效载荷(data)。调用方只需检查 status 字段即可决定后续流程,无需解析多种错误格式。
| 字段 | 类型 | 说明 |
|---|---|---|
| code | int / enum | 业务状态码:0 表示成功,非零表示特定错误类别 |
| message | string | 人类可读的错误描述或成功提示 |
| data | 泛型 / any | 成功时的业务数据,错误时可 null 或附加错误详情 |
模块边界规则: RPC、消息队列、本地方法调用均采用同一 Result 结构,API 网关、BFF 层不做二次转换。
1.2 携带业务状态
状态码的数值空间需要与业务领域对齐,而非仅映射 HTTP 状态码。例如订单模块定义:
// 业务状态码示例
enum OrderCode {
SUCCESS = 0,
INVENTORY_LOW = 1001,
PAYMENT_TIMEOUT = 1002,
SHIPPING_DENIED = 1003
};
每个状态码可在全局注册表中声明其说明与处理策略,便于监控告警自动分类。
1.3 链式错误传播
在多模块串联场景中,下层模块的 Result 需要能够沿着调用链路向上传递,同时保留原始上下文(error chain)。实现方式有两种:
- 嵌套 Result: 下游 Result 作为上游 Result 的 data 中的子对象。
- 因果标识: 每个 Result 携带 trace_id 和父级 error_id,日志系统通过关系建表。
通用做法是在 Result 中加入 cause 字段(可能为 null),指向原始错误 Result。展开后形成一条因果链。
// 链式结构示意(伪代码)
class Result {
int code;
string message;
optional data;
optional Result cause; // 指向原始错误
}
风险点: 链式传播如果保存过长链路,会放大内存开销。落地时需限定最大嵌套深度(如 10 层),超出后截断并记录日志。
1.4 模块边界与数据流
考虑两个服务 A(订单)与 B(库存)的交互:
- A 调用 B 的扣减库存接口,B 返回 Result。
- 若 B 返回 code != 0,A 需要依据 B 的错误码做出补偿动作(如回滚金额),而非仅抛异常。
- 数据流:A 请求 → B 处理 → B 返回 Result → A 检查 code/stauts → 分支处理。
选型权衡: 使用 Result 替代异常机制,会迫使调用方显式处理每个可能失败路径,避免“异常吞没”现象。代价是代码行数增加,但可测试性显著提升。
1.5 核心关系图(SVG)
1.6 落地步骤与验收标准
| 步骤 | 输出 | 验收标准 |
|---|---|---|
| 1. 定义 Result 容器结构 | 类型定义文件(protobuf / Java class / struct) | 所有模块引用同一 jar/package;字段命名严格对齐 |
| 2. 注册业务状态码 | 状态码枚举 + 描述表 | 全局唯一且按服务前缀分配(如 10xx 订单、20xx 库存) |
| 3. 实现链式包装函数 | Result::chain(Result from) 或类似方法 | 单元测试中模拟 5 层嵌套仍可正确读取 cause 链 |
| 4. 集成到 RPC 返回 | 修改 IDL 中的 response 定义 | 所有 RPC 接口 code 字段必填,message 非空 |
| 5. 压测与边界验证 | 性能报告 + 最大深度测试 | 1000 并发下 Result 序列化/反序列化延迟 < 1ms;链深度超过 10 自动截断 |
1.7 风险点
- 状态码膨胀: 团队随意新增码值,导致消费方难以处理。应设置 CR 检查流程。
- 链式引用泄露: 若 cause 字段直接引用下层 Result 对象,GC 无法回收可能造成内存泄漏。改用弱引用或单独存储列表。
- 序列化兼容: 不同语言 SDK 对 Optional 字段的支持不同,需约定缺省值语义。
特别提醒: 不要在 Result 的 data 中再塞入新的 Result 作为整体;应使用
cause 字段专门存储错误链,避免业务数据和链路元数据混杂。