-
Notifications
You must be signed in to change notification settings - Fork 1
PolicyWrap Policy
Unit: Murphy.Policy.Wrap
Policy Wrap combines multiple IExecutablePolicy instances into a single executable policy, applied outermost-first.
Policy Wrap is useful for building a sophisticated resilience strategy out of simpler patterns, such as:
- Retry wrapping Timeout, so a timed-out attempt is retried
- Circuit Breaker wrapping Bulkhead, so persistent rejection opens the circuit
- Any combination of
IExecutablePolicyimplementations, nested to any depth
Note: Unlike other builders, TPolicyWrapBuilder does not derive from TPolicyBuilder<TPolicy> and has no Handle() method - a wrap has no exception filter of its own, since each wrapped policy applies its own filter.
The interface for the Policy Wrap pattern. Being itself an IExecutablePolicy, a wrap can be nested inside another wrap.
type
IPolicyWrap = interface(IExecutablePolicy)
['{DA04B25E-C53F-4C39-BB0D-EFC7E6A5537D}']
end;procedure Execute(AProc: TProc);Description: Executes AProc through every wrapped policy, applied outermost-first.
Behavior: Wrap([A, B]) executes A(B(action)) - A is the outermost policy and sees the composed behavior of everything nested inside it. Internally this is implemented via recursion rather than a pre-built closure chain, so a wrap instance holds no per-call mutable state and can be reused, including concurrently.
Concrete implementation of the Policy Wrap pattern.
type
TPolicyWrap = class(TPolicy, IPolicyWrap)
private
FPolicies: TArray<IExecutablePolicy>;
procedure ExecuteFrom(AIndex: Integer; AProc: TProc);
public
constructor CreateWrap(const APolicies: TArray<IExecutablePolicy>);
procedure Execute(AProc: TProc);
end;constructor CreateWrap(const APolicies: TArray<IExecutablePolicy>);Parameters:
-
APolicies: TArray<IExecutablePolicy>- Policies to combine, outermost-first
Raises: EArgumentException if APolicies is empty
Note: Typically called by TPolicyWrapBuilder.Wrap, not directly by users.
ExecuteFrom recurses through FPolicies by index: at each step it calls the current policy's Execute, passing a closure that recurses into the next index. When the index passes the end of the array, the original action finally runs. Because each recursive call captures only its own index and the immutable FPolicies array, the same TPolicyWrap instance is safe to execute repeatedly and concurrently.
Builder class for creating Policy Wrap instances.
type
TPolicyWrapBuilder = class sealed
public
class function Wrap(const APolicies: TArray<IExecutablePolicy>): IPolicyWrap;
end;class function Wrap(const APolicies: TArray<IExecutablePolicy>): IPolicyWrap;Description: Combines the given policies outermost-first: Wrap([A, B]) executes A(B(action)).
Parameters:
-
APolicies: TArray<IExecutablePolicy>- Policies to combine, outermost-first
Returns: IPolicyWrap - A single executable policy representing the whole chain
Raises: EArgumentException if APolicies is empty
Note: Only types implementing IExecutablePolicy can be wrapped - this includes Retry, Circuit Breaker, Rate Limit, Timeout, Bulkhead, Hedging, and other IPolicyWrap instances. ICachePolicy<TResult> does not implement IExecutablePolicy (it returns a value via TFunc<TResult>) and cannot be wrapped.
uses Murphy.Policy.Retry, Murphy.Policy.Timeout, Murphy.Policy.Wrap;
var
Policy: IPolicyWrap;
begin
Policy := TPolicyWrapBuilder.Wrap([
TRetryBuilder
.Handle(ETimeoutRejectedException)
.Retry(3),
TTimeoutBuilder
.Handle([])
.After(TTimeSpan.FromSeconds(1))
]);
Policy.Execute(procedure begin CallExternalService; end);
end;uses Murphy.Policy.Retry, Murphy.Policy.CircuitBreaker, Murphy.Policy.Bulkhead, Murphy.Policy.Wrap;
var
Policy: IPolicyWrap;
begin
Policy := TPolicyWrapBuilder.Wrap([
TRetryBuilder.Handle(Exception).Retry(3).Wait(TTimeSpan.FromSeconds(1)),
TCircuitBreakerBuilder.Handle(Exception).Fail(5).Within(TTimeSpan.FromSeconds(30)),
TBulkheadBuilder.Handle([]).Limit(10)
]);
// Retry(CircuitBreaker(Bulkhead(action)))
Policy.Execute(procedure begin CallExternalService; end);
end;var
InnerPolicy: IPolicyWrap;
OuterPolicy: IPolicyWrap;
begin
InnerPolicy := TPolicyWrapBuilder.Wrap([
TTimeoutBuilder.Handle([]).After(TTimeSpan.FromSeconds(2)),
TBulkheadBuilder.Handle([]).Limit(5)
]);
OuterPolicy := TPolicyWrapBuilder.Wrap([
TRetryBuilder.Handle(Exception).Retry(3),
InnerPolicy
]);
OuterPolicy.Execute(procedure begin CallExternalService; end);
end;- Policy Wrap Pattern Guide - Comprehensive usage guide with more examples
-
Base Policy API -
IExecutablePolicy, the contract required to participate in a wrap - Combining Patterns - Using Policy Wrap alongside manual composition