In production systems, bad error handling manifests in two extremes:
- Leaking Raw Stack Traces: Returning
NullReferenceExceptionstacks directly to callers, exposing internal SQL schemas and server paths to security exploits; - Returning 200 OK for Errors: Hiding errors behind
{ code: -1, msg: "Failed" }, breaking HTTP protocol semantics and disabling upstream gateway retries.
BitzOrcas.Modern implements “Layered Error Governance + Standard RFC 9457 Problem Details”:
- Expected Business Invariants: Handlers return typed
Result.Failure(Error)mapped automatically to standard 4xx statuses; - Unexpected Infrastructure Crashes: ASP.NET Core
IExceptionHandlerintercepts crashes, redacting stacks and returning standard 500 Problem Details.
Error Governance and Exception Handling Lifecycle
Step 1: Flow Control via Typed Result
Never throw business exceptions! Use typed Result objects for all domain branches:
using System.Threading;using System.Threading.Tasks;using BitzOrcas.Domain.Abstractions;using BitzOrcas.Domain.Results;
public static class OrderErrors{ public static readonly Error NotFound = Error.NotFound("Order.NotFound", "The specified order was not found.");
public static readonly Error CannotCancel = Error.Conflict("Order.CannotCancel", "Completed or shipped orders cannot be cancelled.");}
public sealed class CancelOrderCommandHandler( ICommandRepository<Order, string> orderRepository){ public async ValueTask<Result> Handle(CancelOrderCommand command, CancellationToken ct) { // 1. Load aggregate root var orderResult = await orderRepository.FindAsync(command.OrderId, ct); if (orderResult.IsFailure) { // 2. Resource not found: return typed NotFound error without throwing exceptions return Result.Failure(orderResult.Error); }
var order = orderResult.GetValueOrThrow();
// 3. Evaluate cancellation invariants var cancelResult = order.Cancel(command.Reason); if (cancelResult.IsFailure) { // 4. State conflict: return typed Conflict error return cancelResult; }
await orderRepository.SaveAsync(order, ct); return Result.Success(); }}Step 2: Global Catch-All (GlobalExceptionHandler)
For unexpected database timeouts or null pointers, the global handler sanitizes responses:
using Microsoft.AspNetCore.Diagnostics;using Microsoft.AspNetCore.Http;using Microsoft.Extensions.Logging;
public sealed class GlobalExceptionHandler(ILogger<GlobalExceptionHandler> logger) : IExceptionHandler{ public async ValueTask<bool> TryHandleAsync( HttpContext httpContext, Exception exception, CancellationToken cancellationToken) { var traceId = httpContext.TraceIdentifier;
// 1. Log structured fatal error with full stack trace for engineering alerts logger.LogError(exception, "Unhandled system exception intercepted, TraceId: {TraceId}", traceId);
// 2. Safely emit RFC 9457 compliant 500 response to client httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError; httpContext.Response.ContentType = "application/problem+json";
var problemDetails = new { type = "https://errors.bitzorcas.corp/internal-error", title = "Internal Server Error", status = 500, detail = "An unexpected server error occurred. Please contact support with the trace ID.", traceId };
await httpContext.Response.WriteAsJsonAsync(problemDetails, cancellationToken); return true; }}Summary
BitzOrcas error governance guarantees enterprise resilience:
- Zero Exception Flow Control: Microsecond performance with zero GC allocations;
- Zero Stack Leaks: Sanitizes physical paths and internal schemas from API consumers;
- RFC Compliance: Clean Problem Details JSON for robust client handling.