Skip to content

Commit

Permalink
Add IExecuteWorkflowApi interface and refine retry policy configuration
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
sfmskywalker committed Jan 6, 2024
1 parent c750686 commit 53bd1f9
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ public static IServiceCollection AddElsaClient(this IServiceCollection services,
{
var builderOptions = new ElsaClientBuilderOptions();
configureClient.Invoke(builderOptions);

builderOptions.ConfigureHttpClientBuilder += builder => builder.AddHttpMessageHandler(sp => (DelegatingHandler)sp.GetRequiredService(builderOptions.AuthenticationHandler));

services.AddScoped(builderOptions.AuthenticationHandler);
Expand All @@ -63,7 +62,19 @@ public static IServiceCollection AddElsaClient(this IServiceCollection services,
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);
Expand All @@ -89,14 +100,20 @@ public static IServiceCollection AddElsaClient(this IServiceCollection services,
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>
Expand Down
19 changes: 3 additions & 16 deletions src/clients/Elsa.Api.Client/Options/ElsaClientBuilderOptions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Elsa.Api.Client.HttpMessageHandlers;
using Microsoft.Extensions.DependencyInjection;
using Polly;

namespace Elsa.Api.Client.Options;

Expand Down Expand Up @@ -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))));
}
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

0 comments on commit 53bd1f9

Please sign in to comment.