How to model domain failures, translate them into consistent HTTP responses and preserve diagnostic context without leaking implementation details.

Production principle

Model expected failures explicitly, translate them once at the transport boundary and keep unexpected exceptions observable but private.

Degraded
try:
    order = service.create(payload)
except Exception as exc:
    return {"success": False, "error": str(exc)}  # HTTP 200
Repaired
class OrderConflict(DomainError):
    code = "order_conflict"

@app.exception_handler(DomainError)
async def domain_error_handler(request, exc):
    return JSONResponse(
        status_code=exc.http_status,
        content={"type": exc.code, "title": exc.public_message,
                 "request_id": request.state.request_id},
    )
01

Separate expected and unexpected failure

Expected failures describe valid outcomes of a business operation: a duplicate command, insufficient stock or an invalid state transition. Give them stable types and machine-readable codes so callers can respond without parsing prose.

Unexpected exceptions represent defects, unavailable infrastructure or conditions the application did not model. Log them once with correlation context, return a generic server error and preserve the original cause for investigation.

02

Translate at the transport boundary

Domain code should not raise HTTPException because HTTP is only one possible adapter. A central exception mapping converts known application errors to status codes and a consistent response schema.

Choose status codes by semantics rather than convenience. Validation, conflict, authentication, authorisation, missing resources and transient dependency failure are different contracts. Document representative errors in OpenAPI and test their body as well as status.

03

Design for clients and operators

A client needs a stable code, safe explanation and sometimes structured field details. An operator needs the request identifier, exception chain, affected operation and dependency outcome. Do not force both audiences to share the same payload.

Error evidence includes contract tests, absence of stack traces and secrets in responses, and logs that correlate the public request identifier with the internal failure.

Review checklist

Evidence to take into review

  • Expected business failures have stable typed codes.
  • A single adapter maps application errors to HTTP.
  • Every failure response uses the documented schema.
  • Unexpected exceptions retain correlation without leaking internals.
  • Tests cover status, body and resulting durable state.
Continue the inspection

Explore all engineering notes.

Use PRODUCTION-7 to connect this concern with the other dimensions of a trustworthy backend.

View all articles Get the checklist