From 53bd1f9df952d8013472737feaadfc9fb04e6d12 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Sat, 6 Jan 2024 11:13:29 +0100 Subject: [PATCH] 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. --- .../DependencyInjectionExtensions.cs | 35 ++++++++++++++----- .../Options/ElsaClientBuilderOptions.cs | 19 ++-------- .../Contracts/IExecuteWorkflowApi.cs | 32 +++++++++++++++++ .../Contracts/IWorkflowDefinitionsApi.cs | 20 ----------- 4 files changed, 61 insertions(+), 45 deletions(-) create mode 100644 src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Contracts/IExecuteWorkflowApi.cs diff --git a/src/clients/Elsa.Api.Client/Extensions/DependencyInjectionExtensions.cs b/src/clients/Elsa.Api.Client/Extensions/DependencyInjectionExtensions.cs index 03c70efea..b80136e94 100644 --- a/src/clients/Elsa.Api.Client/Extensions/DependencyInjectionExtensions.cs +++ b/src/clients/Elsa.Api.Client/Extensions/DependencyInjectionExtensions.cs @@ -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(builderOptions); + services.AddApi(builderOptionsWithoutRetryPolicy); services.AddApi(builderOptions); services.AddApi(builderOptions); services.AddApi(builderOptions); @@ -89,14 +100,20 @@ public static class DependencyInjectionExtensions public static void AddApi(this IServiceCollection services, ElsaClientBuilderOptions? httpClientBuilderOptions = default) where T : class { var builder = services.AddRefitClient(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); + } + + /// + /// Adds a refit client for the specified API type. + /// + /// The service collection. + /// An options object that can be used to configure the HTTP client builder. + /// The type representing the API. + public static void AddApiWithoutRetryPolicy(this IServiceCollection services, ElsaClientBuilderOptions? httpClientBuilderOptions = default) where T : class + { + var builder = services.AddRefitClient(CreateRefitSettings, typeof(T).Name).ConfigureHttpClient(ConfigureElsaApiHttpClient); + httpClientBuilderOptions?.ConfigureHttpClientBuilder(builder); } /// diff --git a/src/clients/Elsa.Api.Client/Options/ElsaClientBuilderOptions.cs b/src/clients/Elsa.Api.Client/Options/ElsaClientBuilderOptions.cs index 264fd0637..a7312eb60 100644 --- a/src/clients/Elsa.Api.Client/Options/ElsaClientBuilderOptions.cs +++ b/src/clients/Elsa.Api.Client/Options/ElsaClientBuilderOptions.cs @@ -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 ConfigureHttpClientBuilder { get; set; } = _ => { }; /// - /// Number of automatic retries for transient failures, including following categories: - /// - /// Network failures(as ) - /// HTTP 5XX status codes(server errors) - /// HTTP 408 status code(request timeout) - /// - /// 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. /// - public int TransientHttpErrorRetryCount { get; set; } = 3; - - /// - /// 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)). - /// - public Func SleepDurationProvider = retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)); + public Action? ConfigureRetryPolicy { get; set; } = builder => builder.AddTransientHttpErrorPolicy(p => p.WaitAndRetryAsync(3, attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt)))); } \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Contracts/IExecuteWorkflowApi.cs b/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Contracts/IExecuteWorkflowApi.cs new file mode 100644 index 000000000..9b2ec493c --- /dev/null +++ b/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Contracts/IExecuteWorkflowApi.cs @@ -0,0 +1,32 @@ +using Elsa.Api.Client.Resources.WorkflowDefinitions.Requests; +using JetBrains.Annotations; +using Refit; + +namespace Elsa.Api.Client.Resources.WorkflowDefinitions.Contracts; + +/// +/// Represents a client for the workflow definitions API. +/// +[PublicAPI] +public interface IExecuteWorkflowApi +{ + /// + /// Executes a workflow definition. + /// + /// The definition ID of the workflow definition to execute. + /// An optional request containing options for executing the workflow definition. + /// An optional cancellation token. + /// A response containing information about the workflow instance that was created. + [Post("/workflow-definitions/{definitionId}/execute")] + Task ExecuteAsync(string definitionId, ExecuteWorkflowDefinitionRequest? request, CancellationToken cancellationToken = default); + + /// + /// Dispatches a request to execute the specified workflow definition. + /// + /// The definition ID of the workflow definition to dispatch request. + /// An optional request containing options for dispatching a request to execute the specified workflow definition. + /// An optional cancellation token. + /// A response containing information about the workflow instance that was created. + [Post("/workflow-definitions/{definitionId}/dispatch")] + Task DispatchAsync(string definitionId, DispatchWorkflowDefinitionRequest? request, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Contracts/IWorkflowDefinitionsApi.cs b/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Contracts/IWorkflowDefinitionsApi.cs index 73205909b..b165cec8f 100644 --- a/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Contracts/IWorkflowDefinitionsApi.cs +++ b/src/clients/Elsa.Api.Client/Resources/WorkflowDefinitions/Contracts/IWorkflowDefinitionsApi.cs @@ -196,24 +196,4 @@ public interface IWorkflowDefinitionsApi /// An optional cancellation token. [Post("/workflow-definitions/{definitionId}/revert/{version}")] Task RevertVersionAsync(string definitionId, int version, CancellationToken cancellationToken = default); - - /// - /// Executes a workflow definition. - /// - /// The definition ID of the workflow definition to execute. - /// An optional request containing options for executing the workflow definition. - /// An optional cancellation token. - /// A response containing information about the workflow instance that was created. - [Post("/workflow-definitions/{definitionId}/execute")] - Task ExecuteAsync(string definitionId, ExecuteWorkflowDefinitionRequest? request, CancellationToken cancellationToken = default); - - /// - /// Dispatches a request to execute the specified workflow definition. - /// - /// The definition ID of the workflow definition to dispatch request. - /// An optional request containing options for dispatching a request to execute the specified workflow definition. - /// An optional cancellation token. - /// A response containing information about the workflow instance that was created. - [Post("/workflow-definitions/{definitionId}/dispatch")] - Task DispatchAsync(string definitionId, DispatchWorkflowDefinitionRequest? request, CancellationToken cancellationToken = default); } \ No newline at end of file