Skip to content
Bitzsoft.Integrationsbitzsoft.integrations

Reference

ADR-0004:对外用 Result 包装而非抛异常

新域对外返回 Result 而非抛异常的设计决策,内部用异常在 Provider 边界转换。

Last updated
  • 状态:已接受
  • 日期:2026-07(Payment 域建立时)

背景

集成第三方服务时,失败是常态(网络错误、厂商返回业务错误、签名失败、参数无效)。对外暴露失败信息有两种范式:

  • 抛异常:调用失败时 throw,消费者用 try/catch 处理。
  • 返回 Result:方法返回一个包含成败状态 + 错误码 + 错误信息的结果对象。

本库不同域历史上两种都有:Sms 用 SMSResult(吞异常成 Result),TeamWork 用 TeamWorkException(抛异常)。

决策

新域对外用 Result 包装(推荐),内部用异常,在 Provider 方法边界捕获并转换。

Payment 域是标准范例:PaymentResult<T>IsSuccess / Data / ErrorCode / ErrorMessage + Success() / Fail() 工厂方法)。

public async Task<PaymentResult<PaymentStatusResult>> QueryStatusAsync(string outTradeNo, CancellationToken ct)
{
try
{
var json = await _httpClient.PostAsync(..., ct);
// ① 业务码非成功 → return PaymentResult.Fail(...)
return PaymentResult<PaymentStatusResult>.Success(result);
}
catch (PaymentException ex)
{
// ② 领域异常 → 提取 ErrorCode 和 ProviderMessage
return PaymentResult<PaymentStatusResult>.Fail(ex.ErrorCode, ex.ProviderMessage);
}
catch (HttpRequestException ex)
{
// ③ 网络异常 → 统一错误码
return PaymentResult<PaymentStatusResult>.Fail("HTTP_ERROR", ex.Message);
}
}

理由

  1. 集成失败的预期性:第三方 API 调用失败不是”异常情况”而是”常见分支”,用 Result 比异常更符合语义。
  2. 错误信息结构化ErrorCode + ErrorMessage 比裸异常消息更易被消费者程序化处理(映射到 HTTP 响应、记日志、重试判断)。
  3. 避免吞异常丢失信息:如果用 catch(Exception) 吞成失败 Result,至少保留了错误码和消息。
  4. 异步友好:Result 天然适合 async/await 流程,不引入异常处理的控制流跳跃。

后果

  • Provider 实现要写较多 try/catch 样板代码(每个方法 catch DomainException → catch HttpRequestException → 转 Result)。
  • 消费者需要检查 result.IsSuccess 而非依赖 try/catch,心智模型略有不同。
  • TeamWork 等历史域仍抛异常——风格不统一,但改造成本高,暂不动。
  • OperationCanceledException 不应被吞:取消是控制流语义,不是失败。新代码应单独 catch (OperationCanceledException) 并重抛。

例外

  • 配置校验失败仍用异常(ThrowIfNullOrEmpty / IValidateOptions.Fail)——这属于编程错误,应该 fail-fast。
  • TeamWork 域:历史遗留抛异常范式,未改造。

相关

100%

滚轮或按钮缩放 · 放大后拖动画面 · 双击切换 100% / 200%