Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

OrionError 与稳定身份

本文解释当前实现里的四个概念:

  • DomainReason
  • OrionError
  • ErrorCode
  • ErrorIdentityProvider

核心结论先放在前面:

  • DomainReason 解决“这个 reason 能不能作为 StructError<R> 的运行时语义载体”
  • OrionError 解决“这个 reason 能不能低成本地同时获得显示文案、兼容数值码和稳定身份”
  • ErrorCode 是兼容数值码
  • ErrorIdentityProvider 才是对外稳定协议的机器主键来源

1. 当前 trait 约束

当前 DomainReason 很薄,只要求:

pub trait DomainReason: PartialEq + Display + Debug + Send + Sync + 'static {}

它只负责说明:

  • StructError<R> 的 reason 类型是显式的
  • reason 本身有可显示文本

DomainReason 本身不包含:

  • 稳定 code
  • category
  • exposure 决策
  • 对外协议投影

2. OrionError 做了什么

推荐写法:

#![allow(unused)]
fn main() {
use derive_more::From;
use orion_error::{OrionError, UnifiedReason};

#[derive(Debug, Clone, PartialEq, From, OrionError)]
enum AppReason {
    #[orion_error(identity = "biz.invalid_request")]
    InvalidRequest,
    #[orion_error(transparent)]
    General(UnifiedReason),
}
}

OrionError 会同时生成:

  • Display
  • DomainReason
  • ErrorCode
  • ErrorIdentityProvider

因此它是当前定义领域 reason 的推荐入口。

3. ErrorCodeErrorIdentityProvider 的区别

3.1 ErrorCode

ErrorCode::error_code() 返回兼容数值码:

  • UnifiedReason::system_error()201
  • OrionError 未显式声明 code = ... 时默认是 500

它的定位是:

  • 兼容旧系统
  • 兼容已有数值码测试
  • 保留传统集成接口

3.2 ErrorIdentityProvider

ErrorIdentityProvider 提供两个稳定协议字段:

  • stable_code()
  • error_category()

例如:

  • sys.io_error
  • biz.invalid_request
  • logic.internal_invariant_broken

它们用于:

  • 稳定断言
  • exposure 决策
  • HTTP / CLI / log / RPC 投影
  • 指标、聚合、告警、跨服务协作

如果系统要依赖稳定机器主键,应该依赖这里,而不是 Display 文案。

4. identity 注解规则

4.1 最常用写法

#![allow(unused)]
fn main() {
use orion_error::OrionError;

#[derive(Debug, Clone, PartialEq, OrionError)]
enum AppReason {
    #[orion_error(identity = "biz.order_not_found")]
    OrderNotFound,
}
}

这会产生:

  • stable code:biz.order_not_found
  • 默认显示文案:order not found
  • category:Biz
  • 默认兼容数值码:500

4.2 显式指定 message / code / category

#![allow(unused)]
fn main() {
use orion_error::OrionError;

#[derive(Debug, Clone, PartialEq, OrionError)]
enum AppReason {
    #[orion_error(
        message = "storage temporarily unavailable",
        code = 2000,
        identity = "sys.storage_unavailable",
        category = Sys
    )]
    StorageUnavailable,
}
}

这里:

  • message 控制 Display
  • code 控制兼容数值码
  • identity 控制 stable code
  • category 可显式覆盖,也可由 identity 前缀推导

4.3 transparent

#![allow(unused)]
fn main() {
use orion_error::{OrionError, UnifiedReason};

#[derive(Debug, Clone, PartialEq, OrionError)]
enum AppReason {
    #[orion_error(transparent)]
    General(UnifiedReason),
}
}

transparent 会把以下能力委托给内部单字段类型:

  • Display
  • ErrorCode
  • ErrorIdentityProvider

这适合保留一个 General(UnifiedReason) 兜底通道。

5. 为什么不能只靠 Display

Display 适合给人看,不适合当协议主键。

原因:

  • 文案可能优化
  • 文案可能国际化
  • 文案可能带动态值
  • 不同调用方很难稳定依赖自然语言文本

例如:

  • read config failed
  • failed to read config
  • 读取配置失败

这些都可能描述同一类错误,但它们不适合做稳定协议主键。

相比之下:

  • sys.io_error
  • biz.order_not_found

才适合作为跨边界约定。

6. UnifiedReason 的角色

UnifiedReason 是内置的通用错误分类,已经实现:

  • DomainReason
  • ErrorCode
  • ErrorIdentityProvider

这意味着它可以直接:

  • 作为 StructError<UnifiedReason> 的 reason
  • 作为 #[orion_error(transparent)] 的底层 reason
  • 进入稳定身份和协议投影路径

例如:

  • UnifiedReason::system_error() -> sys.io_error
  • UnifiedReason::network_error() -> sys.network_error
  • UnifiedReason::core_conf() -> conf.core_invalid
  • UnifiedReason::logic_error() -> logic.internal_invariant_broken

7. 什么时候必须提供稳定身份

下面这些情况不要只停在 DomainReason

  • 要使用 identity_snapshot()
  • 要用 assert_err_identity(...)
  • 要输出 HTTP / CLI / log / RPC 响应
  • 要做统一 exposure 决策
  • 要把错误接入指标、聚合、告警
  • 要形成跨 crate 或跨服务的错误契约

这时 reason 应该实现 ErrorIdentityProvider,而最省事的做法通常就是 derive OrionError

8. 推荐模式

推荐让领域 reason 只有少量真正稳定的业务语义,并保留一个通用兜底分支:

#![allow(unused)]
fn main() {
use derive_more::From;
use orion_error::{OrionError, UnifiedReason};

#[derive(Debug, Clone, PartialEq, From, OrionError)]
enum OrderReason {
    #[orion_error(identity = "biz.order_not_found")]
    OrderNotFound,
    #[orion_error(identity = "biz.insufficient_funds")]
    InsufficientFunds,
    #[orion_error(transparent)]
    General(UnifiedReason),
}
}

这样做的好处是:

  • 业务错误有稳定领域语义
  • 通用系统错误不需要重复造轮子
  • HTTP / RPC / log 等投影可以直接复用 UnifiedReason 的稳定身份

9. 设计建议

设计 stable code 时,建议遵守:

  • 不把动态值写进 code
  • 不把实现细节写进 code
  • code 尽量表达语义,不表达调用点
  • category 保持粗粒度

推荐:

  • biz.invalid_request
  • biz.order_not_found
  • conf.feature_invalid
  • sys.io_error

不推荐:

  • read_config_failed_for_user_42
  • primary_db_timeout_us_east_1
  • error_1