* Add resilience module with core interfaces and services Introduced a new `Elsa.Resilience` module and its core components to support resilient services and activities. This includes resilience strategies, providers, and attributes, along with integration into the existing HTTP module for enhanced fault tolerance. Added solution and project references for proper dependency management. * Add resilience strategy framework with HTTP strategy support Introduced a resilience strategy architecture, including a configurable `HttpResilienceStrategy` with retry capabilities, strategy serialization, and integration with existing modules. Enhanced ResilienceFeature to support registration of strategy types and updated application configuration to enable resilience strategies. This change ensures more robust and fault-tolerant HTTP request handling. * Add JSON serialization support for resilience configuration Introduced `ConfigurationExtensions` to enable JSON serialization of configuration sections. Updated resilience strategies to utilize the new extension methods and adjusted JSON serialization logic to support polymorphism with `$type` discriminator. Minor modifications were made to support deserialization and property mutability. * Add resilience strategy support to workflows and API clients Introduced resilience strategy configuration, serialization, and execution support across workflows and API clients. Added new APIs, models, and services to enhance fault tolerance capabilities for activities and HTTP interactions. * Refactor resilience services for improved modularity. Replaced `IResilienceService` with new modular interfaces (`IResilienceStrategyCatalog`, `IResilienceStrategyConfigEvaluator`, `IResilientActivityInvoker`) and corresponding implementations. Enhanced maintainability by simplifying components and responsibilities, ensuring better separation of concerns. * Rename methods in ResilienceStrategyCatalog for clarity Updated method names in `ResilienceStrategyCatalog` and its interfaces for better readability and alignment with naming conventions. Replaced `GetAllStrategiesAsync` with `ListAsync` and `GetStrategyAsync` with `GetAsync` across the codebase. * Refactor resilience handling in HTTP activities. Replaced `ResilienceCategory` property with `ResilienceCategoryAttribute` for a cleaner implementation. Updated `IResilientActivity` to simplify its interface and adjusted related modifications accordingly. Introduced `IResilientActivityInvoker` to enhance resilience strategy execution. * Add support for additional resilience and scripting features Extended resilience strategy handling with serialization support, added `HttpResilienceStrategy` type in JavaScript handler, and refined object conversion logic for interfaces. Minor adjustments to `Expression` class properties for consistency. * Remove `UseResilience` call from Program.cs This call was redundant and no longer necessary for the application. Its removal simplifies the code and ensures only required middleware is used. * Remove commented-out JSON converter code in serializer setup Cleaned up unused and commented-out converter initialization code in `ResilienceStrategySerializer`. This improves readability and removes unnecessary clutter from the file. * Fix typo in method names from 'Resiliency' to 'Resilience' Renamed methods to maintain consistency in naming conventions across the codebase. This change ensures clarity and alignment with established terminology. * Refactor namespace for ConfigurationResilienceStrategySource Updated the namespace of ConfigurationResilienceStrategySource to "StrategySources" for better alignment with naming conventions and structure. Removed an unused namespace reference in ResilienceFeature for cleanup. * Mark EnableResiliency as obsolete in SendHttpRequestBase. The EnableResiliency property is now marked with the [Obsolete] attribute. Developers are encouraged to use the common Resilience Strategy setting instead for managing HTTP request resiliency. This change ensures better consistency and alignment with the broader resilience strategy. * Restrict ResilienceCategoryAttribute to class targets only Removed support for using ResilienceCategoryAttribute on properties. This change enforces a stricter and more focused usage of the attribute, ensuring it applies only to class-level declarations. * Add documentation for IResilientActivityInvoker interface Include summaries and parameter descriptions for the `InvokeAsync` method. This improves code clarity and helps developers understand the functionality and usage of the resilient activity invocation process. * Fix logical operator precedence in type comparison check Parentheses were added to ensure correct evaluation of conditions when checking type compatibility. This prevents potential logical errors when determining the target type in object conversions. * Add support for resilience source identification Introduce the `ResilienceSourceNameAttribute` to allow naming of resilience sources. Updated `ResilienceStrategyCatalog` to utilize the attribute for prefixing strategy IDs, improving source identification and traceability. Applied the attribute to `ConfigurationResilienceStrategySource` as an example. * Revert "Add support for resilience source identification" This reverts commit 19b4e7121d6330b5de4f692b78da4c1e4a2d1f67. * Reapply "Add support for resilience source identification" This reverts commit 8bcba9d040c4eb247077aec6d90dc02817adcbd5. * Revert "Reapply "Add support for resilience source identification"" This reverts commit ee04d35e7930956c752dda3ed150ca34a535e66c.
185 lines
9 KiB
C#
185 lines
9 KiB
C#
using Elsa.Api.Client.Options;
|
|
using Elsa.Api.Client.Resources.ActivityDescriptorOptions.Contracts;
|
|
using Elsa.Api.Client.Resources.ActivityDescriptors.Contracts;
|
|
using Elsa.Api.Client.Resources.ActivityExecutions.Contracts;
|
|
using Elsa.Api.Client.Resources.Alterations.Contracts;
|
|
using Elsa.Api.Client.Resources.CommitStrategies.Contracts;
|
|
using Elsa.Api.Client.Resources.Features.Contracts;
|
|
using Elsa.Api.Client.Resources.Identity.Contracts;
|
|
using Elsa.Api.Client.Resources.IncidentStrategies.Contracts;
|
|
using Elsa.Api.Client.Resources.LogPersistenceStrategies;
|
|
using Elsa.Api.Client.Resources.ResilienceStrategies.Contracts;
|
|
using Elsa.Api.Client.Resources.Scripting.Contracts;
|
|
using Elsa.Api.Client.Resources.StorageDrivers.Contracts;
|
|
using Elsa.Api.Client.Resources.Tasks.Contracts;
|
|
using Elsa.Api.Client.Resources.VariableTypes.Contracts;
|
|
using Elsa.Api.Client.Resources.WorkflowActivationStrategies.Contracts;
|
|
using Elsa.Api.Client.Resources.WorkflowDefinitions.Contracts;
|
|
using Elsa.Api.Client.Resources.WorkflowExecutionContexts.Contracts;
|
|
using Elsa.Api.Client.Resources.WorkflowInstances.Contracts;
|
|
using JetBrains.Annotations;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
|
using Microsoft.Extensions.Options;
|
|
using Refit;
|
|
using static Elsa.Api.Client.RefitSettingsHelper;
|
|
|
|
namespace Elsa.Api.Client.Extensions;
|
|
|
|
/// <summary>
|
|
/// Provides extension methods for dependency injection.
|
|
/// </summary>
|
|
[PublicAPI]
|
|
public static class DependencyInjectionExtensions
|
|
{
|
|
/// <summary>
|
|
/// Adds default Elsa API clients configured to use an API key.
|
|
/// </summary>
|
|
public static IServiceCollection AddDefaultApiClientsUsingApiKey(this IServiceCollection services, Action<ElsaClientOptions> configureOptions)
|
|
{
|
|
var options = new ElsaClientOptions();
|
|
configureOptions(options);
|
|
|
|
return services.AddDefaultApiClients(client =>
|
|
{
|
|
client.BaseAddress = options.BaseAddress;
|
|
client.ApiKey = options.ApiKey;
|
|
client.ConfigureHttpClient = options.ConfigureHttpClient;
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds default Elsa API clients.
|
|
/// </summary>
|
|
public static IServiceCollection AddDefaultApiClients(this IServiceCollection services, Action<ElsaClientBuilderOptions>? configureClient = null)
|
|
{
|
|
return services.AddApiClients(configureClient, builderOptions =>
|
|
{
|
|
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);
|
|
services.AddApi<IActivityExecutionsApi>(builderOptions);
|
|
services.AddApi<IStorageDriversApi>(builderOptions);
|
|
services.AddApi<IVariableTypesApi>(builderOptions);
|
|
services.AddApi<IWorkflowActivationStrategiesApi>(builderOptions);
|
|
services.AddApi<IIncidentStrategiesApi>(builderOptions);
|
|
services.AddApi<ILogPersistenceStrategiesApi>(builderOptions);
|
|
services.AddApi<ICommitStrategiesApi>(builderOptions);
|
|
services.AddApi<IResilienceStrategiesApi>(builderOptions);
|
|
services.AddApi<ILoginApi>(builderOptions);
|
|
services.AddApi<IFeaturesApi>(builderOptions);
|
|
services.AddApi<IJavaScriptApi>(builderOptions);
|
|
services.AddApi<IExpressionDescriptorsApi>(builderOptions);
|
|
services.AddApi<IWorkflowContextProviderDescriptorsApi>(builderOptions);
|
|
services.AddApi<IAlterationsApi>(builderOptions);
|
|
services.AddApi<ITasksApi>(builderOptions);
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds an API client to the service collection. Requires AddElsaClient to be called exactly once.
|
|
/// </summary>
|
|
public static IServiceCollection AddApiClient<T>(this IServiceCollection services, Action<ElsaClientBuilderOptions>? configureClient = null) where T : class
|
|
{
|
|
return services.AddApiClients(configureClient, builderOptions => services.AddApi<T>(builderOptions));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds the Elsa client to the service collection.
|
|
/// </summary>
|
|
public static IServiceCollection AddApiClients(this IServiceCollection services, Action<ElsaClientBuilderOptions>? configureClient = null, Action<ElsaClientBuilderOptions>? configureServices = null)
|
|
{
|
|
var builderOptions = new ElsaClientBuilderOptions();
|
|
configureClient?.Invoke(builderOptions);
|
|
builderOptions.ConfigureHttpClientBuilder += builder => builder.AddHttpMessageHandler(sp => (DelegatingHandler)sp.GetRequiredService(builderOptions.AuthenticationHandler));
|
|
|
|
services.TryAddScoped(builderOptions.AuthenticationHandler);
|
|
|
|
services.Configure<ElsaClientOptions>(options =>
|
|
{
|
|
options.BaseAddress = builderOptions.BaseAddress;
|
|
options.ConfigureHttpClient = builderOptions.ConfigureHttpClient;
|
|
options.ApiKey = builderOptions.ApiKey;
|
|
});
|
|
|
|
configureServices?.Invoke(builderOptions);
|
|
return services;
|
|
}
|
|
|
|
/// <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 IServiceCollection AddApi<T>(this IServiceCollection services, ElsaClientBuilderOptions? httpClientBuilderOptions = default) where T : class
|
|
{
|
|
return services.AddApi(typeof(T), httpClientBuilderOptions);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a refit client for the specified API type.
|
|
/// </summary>
|
|
/// <param name="services">The service collection.</param>
|
|
/// <param name="apiType">The type representing the API</param>
|
|
/// <param name="httpClientBuilderOptions">An options object that can be used to configure the HTTP client builder.</param>
|
|
public static IServiceCollection AddApi(this IServiceCollection services, Type apiType, ElsaClientBuilderOptions? httpClientBuilderOptions = default)
|
|
{
|
|
var builder = services.AddRefitClient(apiType, sp => CreateRefitSettings(sp, httpClientBuilderOptions?.ConfigureJsonSerializerOptions), apiType.Name).ConfigureHttpClient(ConfigureElsaApiHttpClient);
|
|
httpClientBuilderOptions?.ConfigureHttpClientBuilder(builder);
|
|
httpClientBuilderOptions?.ConfigureRetryPolicy?.Invoke(builder);
|
|
return services;
|
|
}
|
|
|
|
/// <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>(sp => CreateRefitSettings(sp), typeof(T).Name)
|
|
.ConfigureHttpClient(ConfigureElsaApiHttpClient);
|
|
httpClientBuilderOptions?.ConfigureHttpClientBuilder(builder);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates an API client for the specified API type.
|
|
/// </summary>
|
|
public static T CreateApi<T>(this IServiceProvider serviceProvider, Uri baseAddress) where T : class
|
|
{
|
|
var httpClientFactory = serviceProvider.GetRequiredService<IHttpClientFactory>();
|
|
var httpClient = httpClientFactory.CreateClient(typeof(T).Name);
|
|
httpClient.BaseAddress = baseAddress;
|
|
return CreateApi<T>(serviceProvider, httpClient);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates an API client for the specified API type.
|
|
/// </summary>
|
|
public static T CreateApi<T>(this IServiceProvider serviceProvider, HttpClient httpClient) where T : class
|
|
{
|
|
return RestService.For<T>(httpClient, CreateRefitSettings(serviceProvider));
|
|
}
|
|
|
|
private static void ConfigureElsaApiHttpClient(IServiceProvider serviceProvider, HttpClient httpClient)
|
|
{
|
|
var options = serviceProvider.GetRequiredService<IOptions<ElsaClientOptions>>().Value;
|
|
httpClient.BaseAddress = options.BaseAddress;
|
|
options.ConfigureHttpClient?.Invoke(serviceProvider, httpClient);
|
|
}
|
|
} |