0

I am using the Circuit Breaker Policy for database access. I have a multi-tenant structure with identical databases that store data for different clients. My application could hit any of the tenants. If one database is down the others may not be. If I open the breaker I want to only open it for that tenant.

I created identical polices for each tenant and store them in a Dictionary with the tenant name as the key. When making a database call, I retrieve the matching policy from the dictionary and execute it.

I am fairly sure this works but I am wondering if it's too convoluted. Perhaps Polly already as a way to do this. Is there a way to bind the policy behavior to a data value for the tenant?

Any advice?

Don Chambers
  • 3,798
  • 9
  • 33
  • 74

1 Answers1

0

The design stated in the question is correct for the requirement: maintain a separate circuit-breaker policy instance per downstream system you want to treat as having distinct health status.

Polly does not offer a circuit-breaker which maintains a big internal dictionary of health states per some key.

Polly does offer a PolicyRegistry; this is fundamentally a key-policy dictionary similar to described in the question.


If the circuit-breaker Policy is required to be used mid-PolicyWrap, and is the only thing in the PolicyWrap that needs to vary per-tenant, you can use a 'policy selector' within the PolicyWrap to select or manufacture an appropriate policy at runtime.

The example code in the link above:

public class AsyncPolicySelector<TResult> : AsyncPolicy<TResult>
{
    private readonly Func<Context, IAsyncPolicy<TResult>> policySelector;

    public static AsyncPolicySelector<TResult> Create(Func<Context, IAsyncPolicy<TResult>> policySelector)
    {
        return new AsyncPolicySelector<TResult>(policySelector);
    }

    private AsyncPolicySelector(Func<Context, IAsyncPolicy<TResult>> policySelector)
    {
        this.policySelector = policySelector;
    }

    protected override Task<TResult> ImplementationAsync(Func<Context, CancellationToken, Task<TResult>> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext)
    {
        return policySelector(context).ExecuteAsync(action, context, cancellationToken, continueOnCapturedContext);
    }
}

creates you a custom AsyncPolicySelector<TResult> policy using a Func<Context, IAsyncPolicy<TResult>> policySelector which can select-a-previous or manufacture afresh (when needed) a policy.

Polly Context has dictionary semantics so you could set the tenant-id on Context prior to execution.

Context context = new Context();
context["TenantId"] = /* tenant id from somewhere */

// (you must execute through the PolicyWrap then with an overload passing in this context)

And use a Func<Context, IAsyncPolicy<TResult>> policySelector to retrieve the circuit-breaker by context["TenantId"] from your dictionary or PolicyRegistry; or manufacture a new circuit-breaker for that tenant if there was not already one.

Peter Csala
  • 17,736
  • 16
  • 35
  • 75
mountain traveller
  • 7,591
  • 33
  • 38