-
Notifications
You must be signed in to change notification settings - Fork 1
Cache Policy
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.
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.
- ICachePolicy<TResult> Interface
- TCachePolicy<TResult> Class
- TCacheBuilder<TResult> Class
- Usage Examples
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.
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;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:
- If a cached value exists and has not expired, returns it immediately
- Otherwise calls
AFuncoutside any lock - If
AFuncsucceeds, stores the result with the current timestamp and returns it - If
AFuncraises a handled exception (perHandle()) and a previous value exists (even an expired one), that stale value is returned instead - If
AFuncraises 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);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
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
Concrete implementation of the Cache policy pattern.
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 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.
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.
Builder class for creating Cache policies with fluent configuration.
type
TCacheBuilder<TResult> = class sealed(TPolicyBuilder<ICachePolicy<TResult>>)
public
class function Handle(AExceptionTypes: TArray<ExceptClass>): ICachePolicy<TResult>; override;
end;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;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;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;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;- Cache Pattern Guide - Comprehensive usage guide with more examples
- Base Policy API - Inherited functionality
- Combining Patterns - Using Cache with other policies