0

We're using the latest Polly to handle our retry and circuit breaker policies for interacting with three APIs.

Basic flow is:
A) Read data from Product Catalogue (API)
B) Get Unique Merchant Token (API)
C) Update Merchant Catalogue (with new item) (API)

Due to the load on Merchant Catalogue API (third party, can't work around this yet!) we get bounced sometimes. Polly is configured to nicely retry this if it fails and circuit breaker pattern style, back off.

We realised that it was being tripped consistently because our Merchant Token was marked as invalid even though the server spat the dummy - the third party marks a token used even on error.

Reading this article which is what we based our solution on initially, we are thinking using the context to reload/refresh the auth token. However I'm a bit confused how I can have a policy that refreshes that token when that logic is not in the wiring up (startup) and instead in the handler that runs the policy.

var authMerchTokenPolicy = Policy<HttpResponseMessage>  
  .HandleResult(r => r.StatusCode == 500)
  .RetryAsync(1, onRetryAsync: async (ex, i, context) => await RefreshMerchantAuthorization(context["httpClient"]));

Is the above example stating that I implement RefreshMerchantAuthorization in the startup class?

I haven't seen a concrete example which is where the confusion lies - and the original developer has since left who wrote this (ironically named Paulie!)

Peter Csala
  • 17,736
  • 16
  • 35
  • 75
Lisa Anna
  • 87
  • 1
  • 9

2 Answers2

0

Is the above example stating that I implement RefreshMerchantAuthorization in the startup class?

Polly's Context class allows you to carry any custom data, with Dictionary<string, object>-like semantics. So you can also pass the handler class in to the policy via the Context.

For RefreshMerchantAuthorization(...) an instance method on a class FooHandler, then you can configure the policy in StartUp:

var authMerchTokenPolicy = Policy<HttpResponseMessage>  
    .HandleResult(r => r.StatusCode == 500)
    .RetryAsync(1, onRetryAsync: async (ex, i, context) => 
        await ((FooHandler)context["handler"]).RefreshMerchantAuthorization(context["httpClient"]));

With, at the policy usage site within FooHandler:

var httpResponseMessage =  
    await authMerchTokenPolicy.ExecuteAsync(context => context["httpClient"].GetAsync(uri), 
    contextData: new Dictionary<string, object> {
        {"httpClient", httpClient},
        {"handler", this} 
    });

This is all assuming the RefreshMerchantAuthorization(...) isn't/can't be made static (if static it can be referenced directly from the StartUp class as a static method).

mountain traveller
  • 7,591
  • 33
  • 38
  • 1
    Clever, I didn't think that would be too safe to pass it like that but i like it. I'm not sure coupling to the startup class would be a good idea though? – Lisa Anna Jan 17 '19 at 09:50
  • 1
    +1. One doesn't have to declare policies in `StartUp`; it's an option that some Polly users like (with `PolicyRegistry`) for policy reuse. It does lead to the coupling here tho, as you say. A consequence flowing from other design choices in Polly: Policies are designed to be immutable, therefore everything about the policy is intentionally declared in one go. OTOH, DI separates policy definition from usage. OTOH again, this use case wants to consume an instance method on a later-instantiated class. Maybe `.RefreshMerchantAuthorization()` as `static` on an indep class could be less coupled? – mountain traveller Jan 17 '19 at 13:44
0

If I understand your question correctly then you are basically interested about how could you call the RefreshMerchantAuthorization method if it is not defined inside the Startup class rather somewhere else?

  1. Let's suppose you have an interface (for example: IRefresher) and an implementation (for example Refresher) which contains the RefreshMerchantAuthorization method.
  2. You have registered these into the DI container (for example: .AddScoped<IRefresher, Refresher>()).
  3. You are using this authMerchTokenPolicy to decorate a pre-configured HttpClient (for example: .AddHttpClient(...).AddPolicyHandler(authMerchTokenPolicy))

Then you can use an overload of AddPolicyHandler which allows you access the IServiceProvider and the HttpRequestMessage:

.AddPolicyHandler((provider, request) => Policy<HttpResponseMessage>  
  .HandleResult(r => r.StatusCode == 500)
  .RetryAsync(1, async (_, __, context) => 
  {
     var refresher = sp.GetRequiredService<IRefresher>();
     await refresher.RefreshMerchantAuthorization(context["httpClient"]);
  }));
Peter Csala
  • 17,736
  • 16
  • 35
  • 75