Add IExecuteWorkflowApi interface and refine retry policy configuration

A new interface, IExecuteWorkflowApi, was created to handle execution and dispatch of workflow definitions. This breaks down functionalities previously present in IWorkflowDefinitionsApi. Also, the retry policy configuration for HTTP requests has been refactored. Instead of hardcoding retry settings, now a delegate method can be optionally passed to customize the behavior. This makes it more flexible and shifts the responsibility of configuring retry policies to the client.
This commit is contained in:
Sipke Schoorstra 2024-01-06 11:13:29 +01:00
parent c750686a4b
commit 53bd1f9df9
4 changed files with 61 additions and 45 deletions

View file

@ -52,7 +52,6 @@ public static class DependencyInjectionExtensions
{
var builderOptions = new ElsaClientBuilderOptions();
configureClient.Invoke(builderOptions);
builderOptions.ConfigureHttpClientBuilder += builder => builder.AddHttpMessageHandler(sp => (DelegatingHandler)sp.GetRequiredService(builderOptions.AuthenticationHandler));
services.AddScoped(builderOptions.AuthenticationHandler);
@ -63,7 +62,19 @@ public static class DependencyInjectionExtensions
options.ConfigureHttpClient = builderOptions.ConfigureHttpClient;
options.ApiKey = builderOptions.ApiKey;
});
var builderOptionsWithoutRetryPolicy = new ElsaClientBuilderOptions
{
ApiKey = builderOptions.ApiKey,
AuthenticationHandler = builderOptions.AuthenticationHandler,
BaseAddress = builderOptions.BaseAddress,
ConfigureHttpClient = builderOptions.ConfigureHttpClient,
ConfigureHttpClientBuilder = builderOptions.ConfigureHttpClientBuilder,
ConfigureRetryPolicy = null
};
services.AddApi<IWorkflowDefinitionsApi>(builderOptions);
services.AddApi<IExecuteWorkflowApi>(builderOptionsWithoutRetryPolicy);
services.AddApi<IWorkflowInstancesApi>(builderOptions);
services.AddApi<IActivityDescriptorsApi>(builderOptions);
services.AddApi<IActivityDescriptorOptionsApi>(builderOptions);
@ -89,14 +100,20 @@ public static class DependencyInjectionExtensions
public static void AddApi<T>(this IServiceCollection services, ElsaClientBuilderOptions? httpClientBuilderOptions = default) where T : class
{
var builder = services.AddRefitClient<T>(CreateRefitSettings, typeof(T).Name).ConfigureHttpClient(ConfigureElsaApiHttpClient);
httpClientBuilderOptions?.ConfigureHttpClientBuilder?.Invoke(builder);
var retryCount = httpClientBuilderOptions?.TransientHttpErrorRetryCount ?? 0;
var sleepDurationProvider = httpClientBuilderOptions?.SleepDurationProvider ?? (retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
if (retryCount > 0)
{
builder.AddTransientHttpErrorPolicy(p => p.WaitAndRetryAsync(retryCount, sleepDurationProvider));
}
httpClientBuilderOptions?.ConfigureHttpClientBuilder(builder);
httpClientBuilderOptions?.ConfigureRetryPolicy?.Invoke(builder);
}
/// <summary>
/// Adds a refit client for the specified API type.
/// </summary>
/// <param name="services">The service collection.</param>
/// <param name="httpClientBuilderOptions">An options object that can be used to configure the HTTP client builder.</param>
/// <typeparam name="T">The type representing the API.</typeparam>
public static void AddApiWithoutRetryPolicy<T>(this IServiceCollection services, ElsaClientBuilderOptions? httpClientBuilderOptions = default) where T : class
{
var builder = services.AddRefitClient<T>(CreateRefitSettings, typeof(T).Name).ConfigureHttpClient(ConfigureElsaApiHttpClient);
httpClientBuilderOptions?.ConfigureHttpClientBuilder(builder);
}
/// <summary>

View file

@ -1,5 +1,6 @@
using Elsa.Api.Client.HttpMessageHandlers;
using Microsoft.Extensions.DependencyInjection;
using Polly;
namespace Elsa.Api.Client.Options;
@ -35,21 +36,7 @@ public class ElsaClientBuilderOptions
public Action<IHttpClientBuilder> ConfigureHttpClientBuilder { get; set; } = _ => { };
/// <summary>
/// Number of automatic retries for transient failures, including following categories:
/// <list type = "bullet" >
/// <item><description> Network failures(as <see cref = "HttpRequestException" />)</description></item>
/// <item><description>HTTP 5XX status codes(server errors)</description></item>
/// <item><description>HTTP 408 status code(request timeout)</description></item>
/// </list>
/// Default value is 3.
/// Set the value to 0 to disable automatic retry.
/// Gets or sets a delegate that can be used to configure the retry policy.
/// </summary>
public int TransientHttpErrorRetryCount { get; set; } = 3;
/// <summary>
/// The function that provides the duration to wait for for each transient failure retry attempt.
/// This option is useless if TransientHttpErrorRetryCount is set to 0.
/// Default strategy is exponential backoff: TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)).
/// </summary>
public Func<int, TimeSpan> SleepDurationProvider = retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt));
public Action<IHttpClientBuilder>? ConfigureRetryPolicy { get; set; } = builder => builder.AddTransientHttpErrorPolicy(p => p.WaitAndRetryAsync(3, attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt))));
}

View file

@ -0,0 +1,32 @@
using Elsa.Api.Client.Resources.WorkflowDefinitions.Requests;
using JetBrains.Annotations;
using Refit;
namespace Elsa.Api.Client.Resources.WorkflowDefinitions.Contracts;
/// <summary>
/// Represents a client for the workflow definitions API.
/// </summary>
[PublicAPI]
public interface IExecuteWorkflowApi
{
/// <summary>
/// Executes a workflow definition.
/// </summary>
/// <param name="definitionId">The definition ID of the workflow definition to execute.</param>
/// <param name="request">An optional request containing options for executing the workflow definition.</param>
/// <param name="cancellationToken">An optional cancellation token.</param>
/// <returns>A response containing information about the workflow instance that was created.</returns>
[Post("/workflow-definitions/{definitionId}/execute")]
Task<HttpResponseMessage> ExecuteAsync(string definitionId, ExecuteWorkflowDefinitionRequest? request, CancellationToken cancellationToken = default);
/// <summary>
/// Dispatches a request to execute the specified workflow definition.
/// </summary>
/// <param name="definitionId">The definition ID of the workflow definition to dispatch request.</param>
/// <param name="request">An optional request containing options for dispatching a request to execute the specified workflow definition.</param>
/// <param name="cancellationToken">An optional cancellation token.</param>
/// <returns>A response containing information about the workflow instance that was created.</returns>
[Post("/workflow-definitions/{definitionId}/dispatch")]
Task<HttpResponseMessage> DispatchAsync(string definitionId, DispatchWorkflowDefinitionRequest? request, CancellationToken cancellationToken = default);
}

View file

@ -196,24 +196,4 @@ public interface IWorkflowDefinitionsApi
/// <param name="cancellationToken">An optional cancellation token.</param>
[Post("/workflow-definitions/{definitionId}/revert/{version}")]
Task RevertVersionAsync(string definitionId, int version, CancellationToken cancellationToken = default);
/// <summary>
/// Executes a workflow definition.
/// </summary>
/// <param name="definitionId">The definition ID of the workflow definition to execute.</param>
/// <param name="request">An optional request containing options for executing the workflow definition.</param>
/// <param name="cancellationToken">An optional cancellation token.</param>
/// <returns>A response containing information about the workflow instance that was created.</returns>
[Post("/workflow-definitions/{definitionId}/execute")]
Task<HttpResponseMessage> ExecuteAsync(string definitionId, ExecuteWorkflowDefinitionRequest? request, CancellationToken cancellationToken = default);
/// <summary>
/// Dispatches a request to execute the specified workflow definition.
/// </summary>
/// <param name="definitionId">The definition ID of the workflow definition to dispatch request.</param>
/// <param name="request">An optional request containing options for dispatching a request to execute the specified workflow definition.</param>
/// <param name="cancellationToken">An optional cancellation token.</param>
/// <returns>A response containing information about the workflow instance that was created.</returns>
[Post("/workflow-definitions/{definitionId}/dispatch")]
Task<HttpResponseMessage> DispatchAsync(string definitionId, DispatchWorkflowDefinitionRequest? request, CancellationToken cancellationToken = default);
}