Learn how Bookify uses the Result pattern for functional error handling without exceptions
The Result pattern is a functional programming approach to error handling that makes failures explicit and type-safe. Instead of throwing exceptions, methods return a Result object that can represent either success or failure.
public class Result{ public bool IsSuccess { get; } public bool IsFailure => !IsSuccess; public Error Error { get; } public static Result Success() => new(true, Error.None); public static Result Failure(Error error) => new(false, error);}
public sealed class Result<TValue> : Result{ private readonly TValue? _value; public TValue Value => IsSuccess ? _value! : throw new InvalidOperationException( "The value of a failure result can not be accessed."); public static Result<TValue> Success<TValue>(TValue value) => new(value, true, Error.None); public static Result<TValue> Failure<TValue>(Error error) => new(default, false, error);}
The Value property throws if accessed on a failed Result. Always check IsSuccess before accessing Value.
Errors in Bookify are represented as immutable records with a code and description:
src/Bookify.Domain/Abstractions/Error.cs
public record Error(string Code, string Name){ public static readonly Error None = new(string.Empty, string.Empty); public static readonly Error NullValue = new("Error.NullValue", "Null value was provided");}
Domain-specific errors are defined as static readonly fields:
src/Bookify.Domain/Bookings/BookingErrors.cs
public static class BookingErrors{ public static readonly Error NotFound = new( "Booking.Found", "The booking with the specified identifier was not found"); public static readonly Error Overlap = new( "Booking.Overlap", "The current booking is overlapping with an existing one"); public static readonly Error NotReserved = new( "Booking.NotReserved", "The booking is not pending"); public static readonly Error AlreadyStarted = new( "Booking.AlreadyStarted", "The booking has already started");}
public sealed record Rating{ public static readonly Error Invalid = new("Rating.Invalid", "The rating is invalid"); private Rating(int value) => Value = value; public int Value { get; init; } public static Result<Rating> Create(int value) { if (value < 1 || value > 5) { return Result.Failure<Rating>(Invalid); } return new Rating(value); }}