领域错误处理策略Skill m13-domain-error

本技能指南详细介绍了在软件系统(特别是后端服务)中设计领域错误处理策略的方法。核心内容包括错误分类(用户错误、内部错误、系统错误、瞬时错误、永久错误)、恢复策略(重试、回退、熔断、超时)、错误上下文传递以及Rust语言下的具体实现模式(如使用thiserror、anyhow、指数退避重试)。关键词:领域错误处理、错误分类、恢复策略、重试机制、熔断器、优雅降级、Rust错误处理、系统韧性。

后端开发 0 次安装 0 次浏览 更新于 2/27/2026

名称: m13-领域错误处理 描述: “用于设计领域错误处理。关键词:领域错误、错误分类、恢复策略、重试、熔断器、优雅降级、领域错误层次结构、面向用户与内部错误、错误代码设计、错误上下文、退避重试、错误恢复、瞬时错误与永久错误” 用户可调用: false

领域错误处理策略

第2层:设计选择

核心问题

谁需要处理这个错误?他们应该如何恢复?

设计错误类型前:

  • 这是面向用户的还是内部的?
  • 恢复是否可能?
  • 调试需要什么上下文?

错误分类

错误类型 受众 恢复方式 示例
面向用户 最终用户 引导操作 无效邮箱未找到
内部错误 开发者 调试信息 数据库错误解析错误
系统错误 运维/SRE 监控/告警 连接超时限流
瞬时错误 自动化 重试 网络错误服务不可用
永久错误 人工 调查 配置无效数据损坏

思考提示

设计错误类型前:

  1. 谁看到这个错误?

    • 最终用户 → 友好消息,可操作
    • 开发者 → 详细,可调试
    • 运维 → 结构化,可告警
  2. 我们能恢复吗?

    • 瞬时错误 → 退避重试
    • 可降级 → 回退值
    • 永久错误 → 快速失败,告警
  3. 需要什么上下文?

    • 调用链 → anyhow::Context
    • 请求ID → 结构化日志
    • 输入数据 → 错误负载

向上追溯 ↑

到领域约束(第3层):

"我应该如何处理支付失败?"
    ↑ 提问:业务规则对重试有何要求?
    ↑ 检查:domain-fintech(交易要求)
    ↑ 检查:SLA(可用性要求)
问题 追溯至 提问
重试策略 domain-* 重试可接受的延迟是多少?
用户体验 domain-* 用户应该看到什么消息?
合规性 domain-* 审计必须记录什么?

向下追溯 ↓

到实现(第1层):

"需要类型化错误"
    ↓ m06-error-handling:库使用thiserror
    ↓ m04-zero-cost:错误枚举设计

"需要错误上下文"
    ↓ m06-error-handling:anyhow::Context
    ↓ 日志记录:带字段的tracing

"需要重试逻辑"
    ↓ m07-concurrency:异步重试模式
    ↓ 库:tokio-retry, backoff

快速参考

恢复模式 适用场景 实现方式
重试 瞬时故障 指数退避
回退 降级模式 缓存/默认值
熔断器 级联故障 failsafe-rs
超时 慢操作 tokio::time::timeout
舱壁隔离 隔离 独立线程池

错误层次结构

#[derive(thiserror::Error, Debug)]
pub enum AppError {
    // 面向用户
    #[error("无效输入: {0}")]
    Validation(String),

    // 瞬时错误(可重试)
    #[error("服务暂时不可用")]
    ServiceUnavailable(#[source] reqwest::Error),

    // 内部错误(记录详情,显示通用)
    #[error("内部错误")]
    Internal(#[source] anyhow::Error),
}

impl AppError {
    pub fn is_retryable(&self) -> bool {
        matches!(self, Self::ServiceUnavailable(_))
    }
}

重试模式

use tokio_retry::{Retry, strategy::ExponentialBackoff};

async fn with_retry<F, T, E>(f: F) -> Result<T, E>
where
    F: Fn() -> impl Future<Output = Result<T, E>>,
    E: std::fmt::Debug,
{
    let strategy = ExponentialBackoff::from_millis(100)
        .max_delay(Duration::from_secs(10))
        .take(5);

    Retry::spawn(strategy, || f()).await
}

常见错误

错误做法 为何错误 更好做法
所有错误相同 不可操作 按受众分类
重试所有错误 浪费资源 仅重试瞬时错误
无限重试 自我DoS 最大尝试次数 + 退避
暴露内部错误 安全风险 用户友好消息
无上下文 难以调试 处处使用.context()

反模式

反模式 为何不好 更好做法
字符串错误 无结构 thiserror类型
对可恢复错误panic! 用户体验差 带上下文的Result
忽略错误 静默失败 记录或传播
处处使用Box<dyn Error> 丢失类型信息 thiserror
在正常路径中处理错误 性能差 早期验证

相关技能

何时需要 参见
错误处理基础 m06-error-handling
重试实现 m07-concurrency
领域建模 m09-domain
面向用户的API domain-*