-
Notifications
You must be signed in to change notification settings - Fork 1
Fallback Policy
Unit: Murphy.Policy.Fallback
The Fallback policy provides alternative results when operations fail, enabling graceful degradation.
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
Generic interface for the Fallback policy pattern.
type
IFallbackPolicy<TResult> = interface(IPolicy)
['{FAF09E9F-03E5-4E80-B1D2-09DDFB0492A3}']
function Execute(AFunc: TFunc<TResult>): TResult;
function Fallback(AFunc: TFunc<TResult>): IFallbackPolicy<TResult>;
end;-
TResult- The return type of the operations (e.g.,string,Integer,TJSONObject)
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:
- Attempts to execute
AFunc - On success: Returns the result
- On handled exception:
- If fallback configured: Returns fallback result
- If no fallback: Re-raises exception
- 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;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;Generic concrete implementation of the Fallback policy pattern.
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;The Execute method (Murphy.Policy.Fallback.pas:46):
- Wraps
AFuncexecution in try-except - On exception:
- Checks if exception is handled (
IsHandled) - Checks if fallback function is assigned
- Returns fallback result if both true
- Re-raises exception otherwise
- Checks if exception is handled (
Generic builder class for creating fallback policies.
type
TFallbackBuilder<TResult> = class sealed(TPolicyBuilder<IFallbackPolicy<TResult>>)
public
class function Handle(AExceptionTypes: TArray<ExceptClass>): IFallbackPolicy<TResult>; override;
end;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;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;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;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;// 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;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;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;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);- Fallback Pattern Guide - Comprehensive usage guide
- Combining Patterns - Using fallback with retry and circuit breaker
- Base Policy API - Inherited functionality
← Circuit Breaker Policy | API Reference Index | Rate Limit Policy →