# Base Policy API **Unit**: `Murphy.Base.Policy` This unit contains the foundational interfaces and base classes used by all Murphy policies. ## Overview The base policy layer provides: - **IPolicy** - The root interface implemented by all policies - **IExecutablePolicy** - Interface for policies that execute a parameterless `TProc`, and can therefore participate in a [Policy Wrap](PolicyWrap-Policy.md) - **TPolicy** - Abstract base class with common functionality - **TPolicyBuilder** - Generic base class for fluent builders All specific policy implementations (Retry, Circuit Breaker, etc.) inherit from these base types. ## Table of Contents - [IPolicy Interface](#ipolicy-interface) - [IExecutablePolicy Interface](#iexecutablepolicy-interface) - [TPolicy Base Class](#tpolicy-base-class) - [TPolicyBuilder Generic Base](#tpolicybuilder-generic-base) - [Usage Examples](#usage-examples) --- ## IPolicy Interface The root interface for all policy types. ### Declaration ```pascal type IPolicy = interface ['{245776D9-D6F2-4472-BD64-C08A9E41C568}'] function IsHandled(AException: Exception): Boolean; end; ``` ### Methods #### IsHandled ```pascal function IsHandled(AException: Exception): Boolean; ``` **Description**: Determines if the policy should handle a given exception. **Parameters**: - `AException: Exception` - The exception to check **Returns**: `Boolean` - `True` - The policy handles this exception type - `False` - The policy does not handle this exception type **Usage**: ```pascal var Policy: IRetryPolicy; Ex: Exception; begin Policy := TRetryBuilder.Handle(EIdHTTPProtocolException).Retry(3).Build; Ex := EIdHTTPProtocolException.Create('Test'); try if Policy.IsHandled(Ex) then WriteLn('This exception will be handled'); finally Ex.Free; end; end; ``` **Notes**: - Uses `InheritsFrom` check, so derived exception types are also handled - Returns `False` if no exception types were configured - Typically you don't call this directly - policies call it internally --- ## IExecutablePolicy Interface Extends `IPolicy` with a single `Execute` method, and is the contract required for a policy to participate in a [Policy Wrap](PolicyWrap-Policy.md). ### Declaration ```pascal type IExecutablePolicy = interface(IPolicy) ['{79460F38-81AD-48CF-8A2A-F10028F86F1B}'] procedure Execute(AProc: TProc); end; ``` ### Methods #### Execute ```pascal procedure Execute(AProc: TProc); ``` **Description**: Executes a parameterless action under the policy's resilience logic. **Parameters**: - `AProc: TProc` - Anonymous procedure to execute **Implementers**: `IRetryPolicy`, `ICircuitBreakerPolicy`, `IRateLimitPolicy`, `ITimeoutPolicy`, `IBulkheadPolicy`, `IHedgingPolicy`, and `IPolicyWrap` (which is itself composable, allowing wraps to nest inside other wraps). **Non-implementers**: `IFallbackPolicy` and `ICachePolicy` do not implement this interface, since their `Execute` method takes a `TFunc` and returns a value instead of taking a `TProc`. As a consequence, Fallback and Cache policies cannot be combined via [Policy Wrap](PolicyWrap-Policy.md). --- ## TPolicy Base Class Abstract base class providing common functionality for all policy implementations. ### Declaration ```pascal type TPolicy = class abstract(TInterfacedObject, IPolicy) private FExceptionTypes: TArray; public constructor Create(AExceptionTypes: TArray); virtual; function IsHandled(AException: Exception): Boolean; end; ``` ### Constructor #### Create ```pascal constructor Create(AExceptionTypes: TArray); virtual; ``` **Description**: Initializes the policy with exception types to handle. **Parameters**: - `AExceptionTypes: TArray` - Array of exception types this policy will handle **Usage**: ```pascal // Typically called by builders, not directly by users var ExceptionTypes: TArray; begin SetLength(ExceptionTypes, 2); ExceptionTypes[0] := EIdHTTPProtocolException; ExceptionTypes[1] := EIdSocketError; // Policy subclass constructor Policy := TRetryPolicy.Create(ExceptionTypes, ...); end; ``` **Notes**: - Marked `virtual` to allow descendant classes to override - Stores the exception types array for later use by `IsHandled` - Users typically don't call this directly - use builders instead ### Methods #### IsHandled ```pascal function IsHandled(AException: Exception): Boolean; ``` **Description**: Implements the `IPolicy.IsHandled` method by checking if the exception inherits from any configured exception type. **Implementation Details**: - Iterates through `FExceptionTypes` array - Uses `InheritsFrom` to check exception hierarchy - Returns `True` on first match - Returns `False` if no match or if `FExceptionTypes` is empty **Source** (`Murphy.Base.Policy.pas:57`): ```pascal function TPolicy.IsHandled(AException: Exception): Boolean; begin if Assigned(FExceptionTypes) then for var LExceptClass in FExceptionTypes do if AException.InheritsFrom(LExceptClass) then begin Result := True; Exit; end; Result := False; end; ``` --- ## TPolicyBuilder Generic Base Generic base class for all policy builders, providing the foundation for fluent configuration. ### Declaration ```pascal type TPolicyBuilder = class public class function Handle(AExceptionType: ExceptClass): TPolicy; overload; virtual; class function Handle(AExceptionTypes: TArray): TPolicy; overload; virtual; abstract; end; ``` ### Type Parameters - `TPolicy: IPolicy` - The policy interface type this builder creates (e.g., `IRetryPolicy`) ### Class Methods #### Handle (Single Exception) ```pascal class function Handle(AExceptionType: ExceptClass): TPolicy; overload; virtual; ``` **Description**: Configures the policy to handle a single exception type. **Parameters**: - `AExceptionType: ExceptClass` - Exception type to handle **Returns**: `TPolicy` - The builder instance for method chaining **Usage**: ```pascal var Policy: IRetryPolicy; begin Policy := TRetryBuilder .Handle(EIdHTTPProtocolException) // Handle single exception type .Retry(3) .Build; end; ``` **Implementation**: Calls the array overload with a single-element array: ```pascal class function TPolicyBuilder.Handle(AExceptionType: ExceptClass): TPolicy; begin Result := Handle([AExceptionType]); end; ``` #### Handle (Multiple Exceptions) ```pascal class function Handle(AExceptionTypes: TArray): TPolicy; overload; virtual; abstract; ``` **Description**: Configures the policy to handle multiple exception types. **Parameters**: - `AExceptionTypes: TArray` - Array of exception types to handle **Returns**: `TPolicy` - The builder instance for method chaining **Usage**: ```pascal var Policy: IRetryPolicy; begin Policy := TRetryBuilder .Handle([ EIdHTTPProtocolException, EIdSocketError, EIdConnClosedGracefully ]) .Retry(3) .Build; end; ``` **Notes**: - Marked `abstract` - must be implemented by descendant builders - Allows specifying multiple exception types in one call - All specified exceptions will be handled by the policy --- ## Usage Examples ### Example 1: Understanding Exception Handling ```pascal uses Murphy.Base.Policy, Murphy.Policy.Retry; var Policy: IRetryPolicy; HttpEx: EIdHTTPProtocolException; SocketEx: EIdSocketError; OtherEx: Exception; begin // Create policy that handles HTTP exceptions Policy := TRetryBuilder .Handle(EIdHTTPProtocolException) .Retry(3) .Build; // Test exception handling HttpEx := EIdHTTPProtocolException.Create('HTTP error'); try WriteLn('HTTP Exception: ', Policy.IsHandled(HttpEx)); // True finally HttpEx.Free; end; SocketEx := EIdSocketError.Create('Socket error'); try WriteLn('Socket Exception: ', Policy.IsHandled(SocketEx)); // False finally SocketEx.Free; end; OtherEx := Exception.Create('Other error'); try WriteLn('Generic Exception: ', Policy.IsHandled(OtherEx)); // False finally OtherEx.Free; end; end; ``` ### Example 2: Handling Multiple Exception Types ```pascal uses Murphy.Base.Policy, Murphy.Policy.CircuitBreaker; var Policy: ICircuitBreakerPolicy; begin // Handle multiple exception types Policy := TCircuitBreakerBuilder .Handle([ EIdHTTPProtocolException, EIdSocketError, EIdConnClosedGracefully ]) .Fail(5) .Within(TTimeSpan.FromSeconds(30)) .Build; // All three exception types will trigger the circuit breaker Policy.Execute( procedure begin MakeNetworkRequest; // May throw any of the handled exceptions end); end; ``` ### Example 3: Exception Inheritance ```pascal // Demonstrates that InheritsFrom works with exception hierarchies var Policy: IRetryPolicy; SpecificEx: EIdHTTPProtocolException; GeneralEx: EIdException; begin // Handle base exception class Policy := TRetryBuilder .Handle(EIdException) // Base class .Retry(2) .Build; // Derived exceptions are also handled SpecificEx := EIdHTTPProtocolException.Create('HTTP error'); try WriteLn('Specific exception handled: ', Policy.IsHandled(SpecificEx)); // True finally SpecificEx.Free; end; GeneralEx := EIdException.Create('General Indy error'); try WriteLn('General exception handled: ', Policy.IsHandled(GeneralEx)); // True finally GeneralEx.Free; end; end; ``` --- ## Design Rationale ### Why Use Interfaces? 1. **Reference Counting**: Automatic memory management 2. **Abstraction**: Decouple interface from implementation 3. **Testability**: Easy to mock for unit tests 4. **Flexibility**: Swap implementations without changing client code ### Why Use Generic Builder Base? 1. **Type Safety**: Compile-time type checking 2. **Code Reuse**: Common functionality in base class 3. **Fluent Interface**: Enable method chaining with correct return types ### Why Abstract Base Class? 1. **Code Reuse**: Share common logic across all policies 2. **Consistency**: Ensure all policies handle exceptions the same way 3. **Extensibility**: Easy to add new policies following the same pattern --- ## See Also - [Architecture](../04-Architecture.md#base-policy-infrastructure) - Design and structure - [Retry Policy API](Retry-Policy.md) - Example of derived policy - [Circuit Breaker Policy API](CircuitBreaker-Policy.md) - Another derived policy - [Policy Wrap API](PolicyWrap-Policy.md) - Composing `IExecutablePolicy` implementations together - [Creating Custom Policies](../04-Architecture.md#adding-a-new-policy-pattern) - Extension guide --- [← API Reference Index](README.md) | [Retry Policy →](Retry-Policy.md)