-
Notifications
You must be signed in to change notification settings - Fork 1
PolicyWrap
Marco Breveglieri edited this page Jul 20, 2026
·
1 revision
Policy Wrap combines multiple executable policies into a single one, so a sophisticated resilience strategy can be built, configured once, and executed as a unit.
- Combining two or more patterns (Retry, Timeout, Circuit Breaker, Bulkhead, Hedging) into a single reusable policy
- Replacing deeply nested manual
Executecalls with a flat, readable composition - Building a resilience strategy that itself needs to be passed around or reused as a single object
- A single pattern is enough - don't wrap just one policy
- Combining
ICachePolicy<TResult>, which is not anIExecutablePolicy(it usesTFunc<TResult>, notTProc) and therefore cannot be part of a wrap
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;Wrap([A, B]) executes A(B(action)) - policies are applied outermost-first. In the example above, Retry is outermost: it retries the whole timed operation whenever it times out.
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;A IPolicyWrap is itself an IExecutablePolicy, so wraps can be composed further:
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;Since a wrap holds no per-call mutable state of its own, the same instance can be executed repeatedly, including concurrently:
var
ResilientCall: IPolicyWrap;
begin
ResilientCall := TPolicyWrapBuilder.Wrap([
TRetryBuilder.Handle(Exception).Retry(2),
TTimeoutBuilder.Handle([]).After(TTimeSpan.FromSeconds(5))
]);
ResilientCall.Execute(procedure begin CallServiceA; end);
ResilientCall.Execute(procedure begin CallServiceB; end);
end;- Order outermost-first - the first policy in the array wraps everything after it; put the policy that should see failures from the whole chain (often Retry) first
-
Only
IExecutablePolicyimplementations qualify - Retry, Circuit Breaker, Rate Limit, Timeout, Bulkhead, Hedging, and other wraps; Cache does not, since it returns a value viaTFunc<TResult> -
Pass at least one policy - an empty array raises
EArgumentException -
Each policy keeps its own exception filter - a wrap has no
Handle()of its own; configure filtering on each wrapped policy individually - Prefer a wrap over manual nesting - it reads top-to-bottom instead of growing indentation with every added pattern