采用std::expected统一错误处理:定义RpcError类型并封装gRPC调用,强制入口函数声明返回类型,通过手动解包、and_then或or_else传播错误,桥接协程时避免throw、统一转为std::unexpected,并支持错误聚合与链路追踪。

在大规模微服务调用链中,业务层需精确区分“可恢复的业务失败”(如用户不存在、库存不足)与“不可恢复的系统异常”(如网络超时、序列化失败),避免错误被静默吞没或强制栈展开破坏调用上下文。
将同步RPC接口统一转为 std::expected 返回
原服务间调用常通过 int 返回码 + out-param 传递结果,易忽略错误检查且无法携带上下文。现全部重构为 std::expected<Response, RpcError> 形式。
第一步:定义统一错误类型 RpcError,继承 std::error_code 并添加 trace_id、service_name 字段,确保跨服务错误可追溯。
第二步:修改 gRPC 客户端存根,将 auto stub->Call(&ctx, req, &resp) 封装为 std::expected<Response, RpcError> safeCall(...)。成功时返回 std::in_place 初始化的 resp;失败时捕获 grpc::Status,映射为 RpcError{status.error_code(), status.error_message(), ctx.trace_id()}。
立即学习“C++免费学习笔记(深入)”;
第三步:在服务入口函数签名中显式声明返回值类型,例如 std::expected<OrderDetail, BusinessError> GetOrder(const OrderId&)。这强制所有调用方必须处理两种状态,【不改签名就无法编译通过】。
构建无异常的跨服务错误透传链路
方法一:手动解包传播(稳定可靠,推荐用于核心链路)
对每个下游调用后立即判断:auto user = fetchUser(id); if (!user.has_value()) return user; —— 这行代码必须紧跟调用之后,否则后续逻辑可能基于无效数据执行。
方法二:封装 and_then 辅助函数(需严格约束类型)
编写模板函数 template<typename T, typename E, typename F> constexpr auto and_then(std::expected<T, E>&& e, F&& f) { if (e.has_value()) return f(std::move(e).value()); else return std::unexpected(std::move(e).error()); }。注意:f 的返回类型必须是 std::expected<U, E>,【E 类型必须完全一致,连 const/volatile 限定符都不能差】。
方法三:使用 or_else 插入降级逻辑
当订单服务不可用时,自动回退到缓存读取:auto order = getOrder(id).or_else([](const BusinessError& e) { if (e.code() == ServiceUnavailable) return loadOrderFromCache(id); else return std::unexpected(e); });。此写法保持返回类型不变,下游无需感知降级发生。
与异步IO和协程层桥接
std::expected 本身不持有执行上下文,因此不能直接包装 async_wait 或 co_await 表达式。需在 awaitable 封装层完成转换。
在底层网络模块中,将 epoll/kqueue 回调中的 errno 错误统一转为 std::error_code,再构造 std::unexpected(std::error_code(errno, std::generic_category()))。
协程函数签名必须返回 std::expected<T, E> 而非 coroutine_handle,例如 task<std::expected<Json, ParseError>> parseJsonAsync(std::string_view buf)。这样上层调用者可用 co_await 获取 std::expected 对象,再做分支处理。
切记:不要在协程内部 throw 异常来表示业务失败,否则会跳出协程帧,丢失 awaiter 上下文。所有业务错误必须走 std::unexpected 分支。
错误聚合与链路追踪注入
在网关或聚合服务中,需合并多个下游调用结果并生成统一错误摘要。
1. 收集所有 std::expected 结果到 std::vector<std::expected<T, AggregatedError>> results。
2. 遍历 results,对每个失败项提取 error(),调用 error().addServiceContext("user-service", "v2.3") 注入来源信息。
3. 若任一结果失败,构造 AggregatedError 包含全部子错误,并设置 trace_id 与父 span_id 关联。最终 return std::unexpected(aggregated);。
这一步的关键是错误对象必须支持 move 语义,否则 vector 存储时拷贝开销过大。AggregatedError 内部用 std::vector<std::unique_ptr<RpcError>> 持有子错误。


















