In this lesson, you will learn how to prevent "leaky abstractions" by isolating error types between distinct architectural layers of your system.
The Problem: Coupling via Errors
When your low-level database module returns the same error type used by your high-level web API, you introduce invisible coupling. If the database implementation changes, your web layer—which might be exposing that error to a client—becomes brittle. You have created a "leaky abstraction" where the caller knows too much about the implementation details of the callee.
Error Domain Separation
Error Domain Separation is the practice of defining specific error types for each logical boundary (or "domain") of your application. Instead of allowing a low-level error to propagate to the top, you map it into an error type meaningful to the current layer.
Consider this service architecture:
// Low-level DB domain
pub enum DbError { ConnectionFailed, QueryTimeout }
// High-level API domain
pub enum ApiError { UserNotFound, ServiceUnavailable }
// The Boundary: Mapping DB errors to API errors
impl From<DbError> for ApiError {
fn from(err: DbError) -> Self {
match err {
DbError::ConnectionFailed => ApiError::ServiceUnavailable,
DbError::QueryTimeout => ApiError::ServiceUnavailable,
}
}
}
Why This Matters
By enforcing this separation, you gain three critical benefits:
- Encapsulation: You can swap your database provider without changing the
ApiErrorenum, because the higher layer remains insulated from the underlying implementation. - Security: You avoid leaking internal infrastructure details (like specific database driver timeouts) to the end-user.
- Reduced Dependency Bloat: Your API layer no longer needs to depend on the crates used by your database layer, preventing unnecessary transitive dependencies.
When a function returns an error, ask yourself: "Does the caller need to know the implementation details, or only the result of the operation within their context?" If it's the latter, map that error to a domain-specific type at the boundary.
Key Takeaway: Never propagate internal implementation details across architectural boundaries; map low-level errors into domain-specific types to decouple your system layers.