惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
美团技术团队
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
月光博客
月光博客
J
Java Code Geeks
Jina AI
Jina AI
罗磊的独立博客
宝玉的分享
宝玉的分享
S
SegmentFault 最新的问题
D
DataBreaches.Net
博客园 - 叶小钗
腾讯CDC
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Last Week in AI
Last Week in AI
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Google DeepMind News
Google DeepMind News
阮一峰的网络日志
阮一峰的网络日志
B
Blog
V
Visual Studio Blog
雷峰网
雷峰网
博客园 - 【当耐特】
Apple Machine Learning Research
Apple Machine Learning Research
Engineering at Meta
Engineering at Meta
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Error Handling Stops Scaling When It Is Treated as a Loca...
seeker-zuo · 2026-05-12 · via DEV Community

Error Handling Stops Scaling When It Is Treated as a Local Coding Habit

Many systems become hard to maintain later not because the business is inherently complex, but because error handling was never designed as part of the system.

At prototype stage, error handling is often just "bubble it up if something fails." But once a system enters long-term evolution, failure paths stop being local control flow. They carry retry decisions, degradation behavior, alert routing, user responses, troubleshooting context, and compatibility expectations. If errors are still just ad hoc strings, local enums, or incidental wrappers, the system becomes brittle.

From a system builder's perspective, the central question is not "how do we throw or return errors?" It is this:

How do we make errors converge for governance while preserving useful information for diagnosis?

This is the problem I have been trying to solve with what I call the Wukong Error Governance Model, and in Rust, with the orion-error crate.


The Core Tension: Governance vs. Diagnostics

Any serious error model has to satisfy two different consumers:

  • Callers need stable, finite classifications so they can retry, degrade, alert, or map boundary responses.
  • Troubleshooters need detailed, reliable information so they can find root cause.

Both are legitimate. They also pull in opposite directions.

If you expose too much low-level detail, upper layers become coupled to the exact failure shapes of databases, network libraries, filesystems, or third-party SDKs. Refactoring the lower layer breaks contracts.

If you only keep top-level business classifications, debugging loses the path: what actually failed, where it failed, what each layer added, and why the final response was produced.

That is why the real problem is not "should we wrap errors?" but:

How can errors converge for governance without collapsing for diagnostics?


Languages Give You Bricks, Not the Building

Every major language ecosystem has explored this problem:

  • Java has exceptions and error codes.
  • Go has explicit error returns, wrapping, and errors.Is / errors.As.
  • Rust has Result<T, E>, ?, enums, and a powerful type system.

These are all useful building blocks. None of them automatically give you an error architecture.

They help you express failure. They do not, by themselves, define:

  • which failures should share one stable identity,
  • which failures should become one governance class,
  • which details should stay internal,
  • which outputs should be exposed at the boundary,
  • and which decisions should be centralized instead of repeated in every handler.

That is the gap between "using error features" and "having error governance."


The Wukong Model

The core idea is simple:

Use stable contracts to make failures governable, reliable diagnostics to preserve root cause, and adaptive output to generate the right view for each receiver.

Internally, that means treating an error as two channels plus a projection step:

internal error model = contract channel + diagnostic channel
adaptive output      = boundary-specific view generated from the internal model

Enter fullscreen mode Exit fullscreen mode

The contract channel carries things like:

  • stable error identity,
  • stable classification,
  • governance attributes such as retryability or exposure level.

The diagnostic channel carries things like:

  • source / cause chain,
  • operation context,
  • structured detail,
  • lower-level failures.

Then boundary outputs such as HTTP responses, CLI output, logs, metrics, or RPC errors are produced from policy, not handcrafted independently in every endpoint.

The key is not making errors more complicated. The key is separating information that should be stable from information that should remain detailed and dynamic.


Why Rust Is a Good Fit

Rust is a strong fit for this model because it already makes failure paths explicit.

  • Result<T, E> keeps error flow in the type system.
  • enums are a natural fit for finite classification spaces.
  • match gives exhaustive handling.
  • generic carriers make it possible to reuse one structured runtime model across domains.

But Rust still does not solve governance automatically.

thiserror, anyhow, and eyre are useful. They help with type generation, propagation ergonomics, and diagnostics. They do not define stable error identity, semantic boundary rules, or centralized output policy for you.

Without those extra constraints, many Rust systems still drift into a familiar pattern:

  • some places return business enums,
  • some places pass lower-level errors through unchanged,
  • some places flatten everything into strings,
  • every handler decides status codes and messages a little differently.

That is error handling, but not yet error governance.


Five Practical Rules for Rust

1. Define reasons by semantic domain, not as one global mega-enum

Do not make one AppError own every failure in the system. Use domain-level reason types instead: RepositoryReason, OrderReason, ParserReason, and so on.

#[derive(Debug, Clone, OrionError)]
enum OrderReason {
    #[orion_error(identity = "order.submit_dependency_unavailable")]
    SubmitDependencyUnavailable,

    #[orion_error(identity = "order.invalid_state")]
    InvalidState,
}

Enter fullscreen mode Exit fullscreen mode

What stays stable is the error identity, not the message wording and not necessarily the enum name itself.

2. Structure errors at first entry

When I/O, network, database, or parse failures enter your own system, that is the moment to do three things at once:

  • choose the current-layer classification,
  • explain the current-layer failure,
  • preserve the lower-level error as source.

If you flatten the lower layer into a string first and reinterpret it later, both governance and diagnostics degrade.

3. Create a new boundary when crossing semantic domains, but preserve the lower layer

Inside one semantic domain, classification can converge. Across domains, a new semantic boundary should usually be created.

For example, a repository connection failure should not leak directly as a repository-level failure to an order service boundary. At the upper layer it may become order.submit_dependency_unavailable, while the repository failure remains preserved in the diagnostic chain.

4. Boundaries should output, not reinterpret

HTTP handlers, RPC endpoints, and CLI entry points should not each decide status codes, messages, and visibility on their own. The boundary should hand the internal error to centralized policy and receive the appropriate output view.

That is how one stable error identity leads to one consistent external behavior.

5. Test error identities, not message prose

Messages change. Wording improves. Text gets redacted or localized. Stable contracts should not depend on that.

assert_eq!(
    err.identity_snapshot().code,
    "order.submit_dependency_unavailable"
);

Enter fullscreen mode Exit fullscreen mode

If tests lock onto prose, every wording change becomes a compatibility hazard. If tests lock onto identity and policy outcomes, the contract becomes enforceable.


What orion-error Is Trying to Do

orion-error is not mainly about saving a few lines of Rust error boilerplate.

It is an attempt to make error governance in Rust operational:

Result<T, StructError<R>>

R              -> contract channel: reason / identity / category
StructError<R> -> diagnostic channel: detail / context / source chain
policy         -> boundary output decisions

Enter fullscreen mode Exit fullscreen mode

In that structure:

  • R expresses stable classification and identity,
  • StructError<R> carries diagnostic information,
  • exposure, report, and snapshot APIs project the internal model into boundary-specific views.

The point is not one clever API. The point is to give industrial Rust systems a consistent model for error propagation, compatibility, exposure, and diagnosis.


Closing Thought

Rust already gives us strong primitives for error handling. But industrial systems need more than Result, ?, and a few convenient derive macros.

They need a durable governance structure for failure paths: one that gives callers stable contracts, gives troubleshooters useful information, keeps boundary behavior consistent, and helps systems resist long-term decay.

That is why I describe orion-error as the Rust implementation of the Wukong Error Governance Model.

orion-error github repo