Skip to content

PolicyWrap

Marco Breveglieri edited this page Jul 20, 2026 · 1 revision

Policy Wrap Pattern Guide

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.

When to Use

  • Combining two or more patterns (Retry, Timeout, Circuit Breaker, Bulkhead, Hedging) into a single reusable policy
  • Replacing deeply nested manual Execute calls with a flat, readable composition
  • Building a resilience strategy that itself needs to be passed around or reused as a single object

When NOT to Use

  • A single pattern is enough - don't wrap just one policy
  • Combining ICachePolicy<TResult>, which is not an IExecutablePolicy (it uses TFunc<TResult>, not TProc) and therefore cannot be part of a wrap

Quick Start

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.

Common Scenarios

Retry + Circuit Breaker + Bulkhead

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;

Nesting a Wrap Inside Another Wrap

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;

Reusing a Wrap Across Calls

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;

Best Practices

  1. 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
  2. Only IExecutablePolicy implementations qualify - Retry, Circuit Breaker, Rate Limit, Timeout, Bulkhead, Hedging, and other wraps; Cache does not, since it returns a value via TFunc<TResult>
  3. Pass at least one policy - an empty array raises EArgumentException
  4. Each policy keeps its own exception filter - a wrap has no Handle() of its own; configure filtering on each wrapped policy individually
  5. Prefer a wrap over manual nesting - it reads top-to-bottom instead of growing indentation with every added pattern

See Also


Back to Index

Clone this wiki locally