Skip to content

Cache Policy

Marco Breveglieri edited this page Jul 20, 2026 · 1 revision

Cache Policy API

Unit: Murphy.Policy.Cache

The Cache policy caches the result of an expensive operation for a configurable duration, serving the last known good value when a refresh fails with a handled exception.

Overview

The Cache pattern is useful for reducing load on expensive or remote operations, such as:

  • Fetching remote configuration
  • Expensive lookups that don't need to be up-to-the-second
  • Degrading gracefully to stale data when a refresh fails

Note: Unlike most other policies, ICachePolicy<TResult> implements IPolicy directly rather than IExecutablePolicy, because its Execute method takes a TFunc<TResult> and returns a value instead of taking a TProc. It therefore cannot be combined via Policy Wrap.

Table of Contents


ICachePolicy<TResult> Interface

The generic interface for the Cache policy pattern. Each policy instance caches a single value; create one policy per operation whose result must be cached.

Declaration

type
  ICachePolicy<TResult> = interface(IPolicy)
    ['{4988F6C2-CB95-466E-8F6B-2BCFE8776014}']
    function Execute(AFunc: TFunc<TResult>): TResult;
    function Expire(ADuration: TTimeSpan): ICachePolicy<TResult>;
    procedure Invalidate;
  end;

Methods

Execute

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

Description: Returns the cached value if present and not expired; otherwise invokes AFunc to refresh it.

Parameters:

  • AFunc: TFunc<TResult> - Function producing the value to cache

Returns: TResult - The cached or freshly computed value

Behavior:

  1. If a cached value exists and has not expired, returns it immediately
  2. Otherwise calls AFunc outside any lock
  3. If AFunc succeeds, stores the result with the current timestamp and returns it
  4. If AFunc raises a handled exception (per Handle()) and a previous value exists (even an expired one), that stale value is returned instead
  5. If AFunc raises an unhandled exception, or no previous value exists, the exception is re-raised

Note: Concurrent cache misses may each invoke AFunc; the last completed call wins the cache slot. This is a deliberate trade-off to avoid holding a lock during the (potentially slow) refresh.

Example:

Value := Policy.Execute(
  function: string
  begin
    Result := FetchExpensiveData;
  end);

Expire

function Expire(ADuration: TTimeSpan): ICachePolicy<TResult>;

Description: Configures how long a cached value stays fresh before a refresh is attempted.

Parameters:

  • ADuration: TTimeSpan - Time-to-live for a cached value

Returns: ICachePolicy<TResult> - Self for method chaining

Default: 5 minutes if not specified

Invalidate

procedure Invalidate;

Description: Clears the cached value immediately, forcing the next Execute call to refresh regardless of expiry.

Use Cases: Forcing a fresh read right after a known upstream write


TCachePolicy<TResult> Class

Concrete implementation of the Cache policy pattern.

Declaration

type
  TCachePolicy<TResult> = class(TPolicy, ICachePolicy<TResult>)
  private
    FCachedAt: TDateTime;
    FCachedValue: TResult;
    FExpireDuration: TTimeSpan;
    FHasValue: Boolean;
  protected
    function IsExpired: Boolean;
  public
    constructor Create(AExceptionTypes: TArray<ExceptClass>); override;
    function Execute(AFunc: TFunc<TResult>): TResult;
    function Expire(ADuration: TTimeSpan): ICachePolicy<TResult>;
    procedure Invalidate;
  end;

Constructor

constructor Create(AExceptionTypes: TArray<ExceptClass>); override;

Defaults:

  • FCachedAt: MinDateTime (no cached value yet)
  • FExpireDuration: 5 minutes
  • FHasValue: False

Note: Typically called by TCacheBuilder<TResult>, not directly by users.

Implementation Details

IsExpired compares FCachedAt + FExpireDuration against Scheduler.Now (respects test mode via Murphy.Services.Schedulers). Reads and writes of the cached state are synchronized via TMonitor on the policy instance; the refresh call itself runs outside the lock.

Note: When TResult is a class type, the policy does not take ownership of cached instances - prefer interfaces or records for TResult to avoid lifetime issues.


TCacheBuilder<TResult> Class

Builder class for creating Cache policies with fluent configuration.

Declaration

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

Handle

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

Description: Creates a new Cache policy instance. The exception types passed here select which refresh failures may be answered with the stale cached value: on a handled exception the last cached value (if any) is returned; any other exception is re-raised.

Returns: ICachePolicy<TResult> - Policy instance ready for further configuration

Usage:

// Serve stale data on network errors, but not on other failures
Policy := TCacheBuilder<string>
  .Handle(ENetworkError)
  .Expire(TTimeSpan.FromMinutes(5))
  .Build;

Usage Examples

Example 1: Basic Cache

uses
  Murphy.Policy.Cache;

var
  Policy: ICachePolicy<string>;
  Value: string;
begin
  Policy := TCacheBuilder<string>
    .Handle(ENetworkError)
    .Expire(TTimeSpan.FromMinutes(5))
    .Build;

  Value := Policy.Execute(
    function: string
    begin
      Result := FetchExpensiveData;
    end);
end;

Example 2: Manual Invalidation After a Write

var
  Policy: ICachePolicy<string>;
begin
  Policy := TCacheBuilder<string>.Handle(Exception).Expire(TTimeSpan.FromMinutes(10)).Build;

  UpdateRemoteConfig(NewValue);
  Policy.Invalidate; // Force the next read to refresh
end;

Example 3: Caching a Record Type

type
  TUserProfile = record
    Name: string;
    Email: string;
  end;

var
  Policy: ICachePolicy<TUserProfile>;
  Profile: TUserProfile;
begin
  Policy := TCacheBuilder<TUserProfile>
    .Handle(Exception)
    .Expire(TTimeSpan.FromMinutes(2))
    .Build;

  Profile := Policy.Execute(
    function: TUserProfile
    begin
      Result := FetchUserProfile;
    end);
end;

See Also


← Bulkhead Policy | API Reference Index | Hedging Policy →

Clone this wiki locally