In this lesson, you will learn how to encapsulate opaque errors from external dependencies into domain-specific exceptions to make your application’s error handling more predictable and maintainable.
The Problem with Opaque Errors
When you use a third-party library, it often throws errors specific to its own internal implementation. If you let these "leak" through your layers, your core business logic ends up coupled to the library's quirks. For example, if a database driver throws a MongoError because of a connection timeout, a high-level service should not need to know about MongoDB-specific classes to handle that failure.
The Wrapping Pattern
Instead of letting these raw errors propagate, you should catch them at the boundary of your infrastructure layer and re-throw them as application-specific error types. This allows you to normalize different external errors into a standard format your system understands.
Consider this example where we translate a generic library error into a domain-specific exception:
// A custom domain error
class DatabaseOperationError extends Error {
constructor(message, statusCode = 500) {
super(message);
this.statusCode = statusCode;
this.name = 'DatabaseOperationError';
}
}
async function getUser(id) {
try {
return await thirdPartyDb.fetch(id);
} catch (err) {
// We mask the library's internal error with our own context
throw new DatabaseOperationError(
`Failed to retrieve user ${id} from store.`,
503
);
}
}
Why This Matters
By wrapping these exceptions, you gain several benefits:
- Abstraction: Your service layer doesn't need to know the specific library implementation.
- Standardization: You can attach consistent properties like
statusCodeorisRetryableto all exceptions, which simplifies your global exception handling logic. - Traceability: You can add context, such as the specific record ID or the operation being performed, which is often missing from the raw library error.
If you ever swap out your third-party library, you only need to update the mapping logic in your infrastructure layer, leaving your business logic completely untouched.
Key Takeaway: Always catch third-party errors at the boundary and translate them into custom, application-specific exceptions to preserve domain integrity and simplify upstream error handling.