# 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` implements `IPolicy` directly rather than `IExecutablePolicy`, because its `Execute` method takes a `TFunc` and returns a value instead of taking a `TProc`. It therefore **cannot** be combined via [Policy Wrap](PolicyWrap-Policy.md). ## Table of Contents - [ICachePolicy\ Interface](#icachepolicytresult-interface) - [TCachePolicy\ Class](#tcachepolicytresult-class) - [TCacheBuilder\ Class](#tcachebuildertresult-class) - [Usage Examples](#usage-examples) --- ## ICachePolicy\ 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 ```pascal type ICachePolicy = interface(IPolicy) ['{4988F6C2-CB95-466E-8F6B-2BCFE8776014}'] function Execute(AFunc: TFunc): TResult; function Expire(ADuration: TTimeSpan): ICachePolicy; procedure Invalidate; end; ``` ### Methods #### Execute ```pascal function Execute(AFunc: TFunc): TResult; ``` **Description**: Returns the cached value if present and not expired; otherwise invokes `AFunc` to refresh it. **Parameters**: - `AFunc: TFunc` - 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**: ```pascal Value := Policy.Execute( function: string begin Result := FetchExpensiveData; end); ``` #### Expire ```pascal function Expire(ADuration: TTimeSpan): ICachePolicy; ``` **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` - Self for method chaining **Default**: `5 minutes` if not specified #### Invalidate ```pascal 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\ Class Concrete implementation of the Cache policy pattern. ### Declaration ```pascal type TCachePolicy = class(TPolicy, ICachePolicy) private FCachedAt: TDateTime; FCachedValue: TResult; FExpireDuration: TTimeSpan; FHasValue: Boolean; protected function IsExpired: Boolean; public constructor Create(AExceptionTypes: TArray); override; function Execute(AFunc: TFunc): TResult; function Expire(ADuration: TTimeSpan): ICachePolicy; procedure Invalidate; end; ``` ### Constructor ```pascal constructor Create(AExceptionTypes: TArray); override; ``` **Defaults**: - `FCachedAt`: `MinDateTime` (no cached value yet) - `FExpireDuration`: `5 minutes` - `FHasValue`: `False` **Note**: Typically called by `TCacheBuilder`, 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\ Class Builder class for creating Cache policies with fluent configuration. ### Declaration ```pascal type TCacheBuilder = class sealed(TPolicyBuilder>) public class function Handle(AExceptionTypes: TArray): ICachePolicy; override; end; ``` #### Handle ```pascal class function Handle(AExceptionTypes: TArray): ICachePolicy; 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` - Policy instance ready for further configuration **Usage**: ```pascal // Serve stale data on network errors, but not on other failures Policy := TCacheBuilder .Handle(ENetworkError) .Expire(TTimeSpan.FromMinutes(5)) .Build; ``` --- ## Usage Examples ### Example 1: Basic Cache ```pascal uses Murphy.Policy.Cache; var Policy: ICachePolicy; Value: string; begin Policy := TCacheBuilder .Handle(ENetworkError) .Expire(TTimeSpan.FromMinutes(5)) .Build; Value := Policy.Execute( function: string begin Result := FetchExpensiveData; end); end; ``` ### Example 2: Manual Invalidation After a Write ```pascal var Policy: ICachePolicy; begin Policy := TCacheBuilder.Handle(Exception).Expire(TTimeSpan.FromMinutes(10)).Build; UpdateRemoteConfig(NewValue); Policy.Invalidate; // Force the next read to refresh end; ``` ### Example 3: Caching a Record Type ```pascal type TUserProfile = record Name: string; Email: string; end; var Policy: ICachePolicy; Profile: TUserProfile; begin Policy := TCacheBuilder .Handle(Exception) .Expire(TTimeSpan.FromMinutes(2)) .Build; Profile := Policy.Execute( function: TUserProfile begin Result := FetchUserProfile; end); end; ``` --- ## See Also - [Cache Pattern Guide](../Patterns/Cache.md) - Comprehensive usage guide with more examples - [Base Policy API](Base-Policy.md) - Inherited functionality - [Combining Patterns](../Patterns/Combining-Patterns.md) - Using Cache with other policies --- [← Bulkhead Policy](Bulkhead-Policy.md) | [API Reference Index](README.md) | [Hedging Policy →](Hedging-Policy.md)