Skip to content

Fallback Policy

Marco Breveglieri edited this page Nov 24, 2025 · 1 revision

Fallback Policy API

Unit: Murphy.Policy.Fallback

The Fallback policy provides alternative results when operations fail, enabling graceful degradation.

Overview

The Fallback pattern answers the question: "What should happen when an operation fails completely?" Instead of failing, return:

  • Cached data
  • Default values
  • Simplified/degraded functionality
  • Data from an alternative source

Table of Contents


IFallbackPolicy Interface

Generic interface for the Fallback policy pattern.

Declaration

type
  IFallbackPolicy<TResult> = interface(IPolicy)
    ['{FAF09E9F-03E5-4E80-B1D2-09DDFB0492A3}']
    function Execute(AFunc: TFunc<TResult>): TResult;
    function Fallback(AFunc: TFunc<TResult>): IFallbackPolicy<TResult>;
  end;

Type Parameters

  • TResult - The return type of the operations (e.g., string, Integer, TJSONObject)

Methods

Execute

function Execute(AFunc: TFunc<TResult>): TResult;

Description: Executes the provided function, returning the fallback result if it fails.

Parameters:

  • AFunc: TFunc<TResult> - Function to execute

Returns: TResult - Either the result of AFunc or the fallback value

Behavior:

  1. Attempts to execute AFunc
  2. On success: Returns the result
  3. On handled exception:
    • If fallback configured: Returns fallback result
    • If no fallback: Re-raises exception
  4. On unhandled exception: Re-raises immediately

Example:

var
  Policy: IFallbackPolicy<string>;
  Data: string;
begin
  Policy := TFallbackBuilder<string>
    .Handle(Exception)
    .Fallback(function: string
              begin
                Result := 'Default Value';
              end)
    .Build;

  Data := Policy.Execute(
    function: string
    begin
      Result := FetchDataFromAPI;  // May fail
    end);

  WriteLn(Data);  // Either API data or 'Default Value'
end;

Fallback

function Fallback(AFunc: TFunc<TResult>): IFallbackPolicy<TResult>;

Description: Configures the fallback function that provides alternative results.

Parameters:

  • AFunc: TFunc<TResult> - Function that returns the fallback value

Returns: IFallbackPolicy<TResult> - Self for method chaining

Notes:

  • Must be configured before calling Execute, otherwise exceptions are re-raised
  • Fallback function should ideally not throw exceptions
  • Can access closures/captured variables

Example:

var
  CachedData: string;
  Policy: IFallbackPolicy<string>;
begin
  CachedData := 'Cached Value';

  Policy := TFallbackBuilder<string>
    .Handle(Exception)
    .Fallback(function: string
              begin
                WriteLn('Using cached data');
                Result := CachedData;  // Access captured variable
              end)
    .Build;
end;

TFallbackPolicy Class

Generic concrete implementation of the Fallback policy pattern.

Declaration

type
  TFallbackPolicy<TResult> = class(TPolicy, IFallbackPolicy<TResult>)
  private
    FFallbackFunc: TFunc<TResult>;
  public
    function Execute(AFunc: TFunc<TResult>): TResult;
    function Fallback(AFunc: TFunc<TResult>): IFallbackPolicy<TResult>;
  end;

Implementation Details

The Execute method (Murphy.Policy.Fallback.pas:46):

  1. Wraps AFunc execution in try-except
  2. On exception:
    • Checks if exception is handled (IsHandled)
    • Checks if fallback function is assigned
    • Returns fallback result if both true
    • Re-raises exception otherwise

TFallbackBuilder Class

Generic builder class for creating fallback policies.

Declaration

type
  TFallbackBuilder<TResult> = class sealed(TPolicyBuilder<IFallbackPolicy<TResult>>)
  public
    class function Handle(AExceptionTypes: TArray<ExceptClass>): IFallbackPolicy<TResult>; override;
  end;

Class Methods

Handle

class function Handle(AExceptionTypes: TArray<ExceptClass>): IFallbackPolicy<TResult>;

Description: Creates a new fallback policy for the specified result type and exception types.

Usage:

// For string results
Policy := TFallbackBuilder<string>
  .Handle(Exception)
  .Fallback(function: string begin Result := 'default'; end)
  .Build;

// For integer results
PolicyInt := TFallbackBuilder<Integer>
  .Handle(Exception)
  .Fallback(function: Integer begin Result := 0; end)
  .Build;

// For object results
PolicyObj := TFallbackBuilder<TJSONObject>
  .Handle(Exception)
  .Fallback(function: TJSONObject begin Result := TJSONObject.Create; end)
  .Build;

Usage Examples

Example 1: Simple Fallback to Default Value

var
  Policy: IFallbackPolicy<string>;
  Username: string;
begin
  Policy := TFallbackBuilder<string>
    .Handle(Exception)
    .Fallback(function: string
              begin
                Result := 'Guest';  // Default username
              end)
    .Build;

  Username := Policy.Execute(
    function: string
    begin
      Result := GetUsernameFromSession;  // May fail
    end);

  WriteLn('Welcome, ', Username);  // Either real username or 'Guest'
end;

Example 2: Fallback to Cached Data

type
  TDataService = class
  private
    FCache: TJSONObject;
  public
    function GetData: TJSONObject;
  end;

function TDataService.GetData: TJSONObject;
var
  Policy: IFallbackPolicy<TJSONObject>;
begin
  Policy := TFallbackBuilder<TJSONObject>
    .Handle([EIdHTTPProtocolException, EIdSocketError])
    .Fallback(function: TJSONObject
              begin
                WriteLn('API unavailable - using cached data');
                Result := FCache.Clone as TJSONObject;
              end)
    .Build;

  Result := Policy.Execute(
    function: TJSONObject
    begin
      Result := FetchFromAPI;  // Try live data first
    end);
end;

Example 3: Fallback Chain (Multiple Fallbacks)

var
  Policy1, Policy2: IFallbackPolicy<string>;
  Data: string;
begin
  // Inner fallback: Try database, fall back to default
  Policy1 := TFallbackBuilder<string>
    .Handle(EDatabaseError)
    .Fallback(function: string
              begin
                Result := 'System Default';
              end)
    .Build;

  // Outer fallback: Try API, fall back to database/default
  Policy2 := TFallbackBuilder<string>
    .Handle(EIdHTTPProtocolException)
    .Fallback(function: string
              begin
                Result := Policy1.Execute(
                  function: string
                  begin
                    Result := GetFromDatabase;
                  end);
              end)
    .Build;

  // Tries: API → Database → Default
  Data := Policy2.Execute(
    function: string
    begin
      Result := GetFromAPI;
    end);
end;

Example 4: Fallback with Different Result Types

// String fallback
var StringPolicy := TFallbackBuilder<string>
  .Handle(Exception)
  .Fallback(function: string begin Result := ''; end)
  .Build;

// Integer fallback
var IntPolicy := TFallbackBuilder<Integer>
  .Handle(Exception)
  .Fallback(function: Integer begin Result := -1; end)
  .Build;

// Boolean fallback
var BoolPolicy := TFallbackBuilder<Boolean>
  .Handle(Exception)
  .Fallback(function: Boolean begin Result := False; end)
  .Build;

// Object fallback (be careful with memory management!)
var ObjPolicy := TFallbackBuilder<TStringList>
  .Handle(Exception)
  .Fallback(function: TStringList
            begin
              Result := TStringList.Create;
              Result.Add('Fallback Data');
            end)
  .Build;

Example 5: Conditional Fallback

var
  Policy: IFallbackPolicy<string>;
  LastKnownGood: string;
begin
  LastKnownGood := 'Last successful response';

  Policy := TFallbackBuilder<string>
    .Handle(Exception)
    .Fallback(function: string
              begin
                if LastKnownGood <> '' then
                begin
                  WriteLn('Using last known good value');
                  Result := LastKnownGood;
                end
                else
                begin
                  WriteLn('No cached data, using default');
                  Result := 'No data available';
                end;
              end)
    .Build;

  var Data := Policy.Execute(
    function: string
    begin
      Result := FetchLiveData;
    end);
end;

Important Notes

Memory Management

When using fallback with objects, ensure proper memory management:

// WRONG - Potential memory leak
Policy := TFallbackBuilder<TStringList>
  .Handle(Exception)
  .Fallback(function: TStringList
            begin
              Result := TStringList.Create;  // Who owns this?
            end)
  .Build;

// BETTER - Clear ownership
var FallbackList: TStringList;
begin
  FallbackList := TStringList.Create;
  try
    Policy := TFallbackBuilder<TStringList>
      .Handle(Exception)
      .Fallback(function: TStringList
                begin
                  Result := FallbackList;  // Return existing instance
                end)
      .Build;

    var Data := Policy.Execute(function: TStringList begin ... end);
  finally
    // Clean up based on ownership rules
  end;
end;

Type Safety

Delphi's generic system ensures type safety:

// Compile-time type checking
var Policy := TFallbackBuilder<Integer>.Handle(Exception).Build;

// This won't compile - type mismatch
// Policy.Fallback(function: string begin Result := 'test'; end);

// This compiles - correct type
Policy.Fallback(function: Integer begin Result := 0; end);

See Also


← Circuit Breaker Policy | API Reference Index | Rate Limit Policy →

Clone this wiki locally