-
Notifications
You must be signed in to change notification settings - Fork 284
Durable Client Factory (DurableOrchestrationClient outside Azure Functions ) #1125
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
6665e38
1cc8800
aa4fa32
b189084
8fff713
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| // Copyright (c) .NET Foundation. All rights reserved. | ||
| // Licensed under the MIT License. See LICENSE in the project root for license information. | ||
|
|
||
| using System; | ||
| using System.Collections.Concurrent; | ||
| using Microsoft.Azure.WebJobs.Extensions.DurableTask.Options; | ||
| using Microsoft.Extensions.Logging; | ||
| using Microsoft.Extensions.Options; | ||
| using Newtonsoft.Json; | ||
|
|
||
| namespace Microsoft.Azure.WebJobs.Extensions.DurableTask.ContextImplementations | ||
| { | ||
| /// <summary> | ||
| /// Factory class to create Durable Client to start works outside an azure function context. | ||
| /// </summary> | ||
| public class DurableClientFactory : IDurableClientFactory, IDisposable | ||
| { | ||
| // Creating client objects is expensive, so we cache them when the attributes match. | ||
| // Note that DurableClientAttribute defines a custom equality comparer. | ||
| private readonly ConcurrentDictionary<DurableClientAttribute, DurableClient> cachedClients = | ||
| new ConcurrentDictionary<DurableClientAttribute, DurableClient>(); | ||
|
|
||
| private readonly ConcurrentDictionary<DurableClientAttribute, HttpApiHandler> cachedHttpListeners = | ||
| new ConcurrentDictionary<DurableClientAttribute, HttpApiHandler>(); | ||
|
|
||
| private readonly DurableClientOptions defaultDurableClientOptions; | ||
| private readonly DurableTaskOptions durableTaskOptions; | ||
| private readonly IDurabilityProviderFactory durabilityProviderFactory; | ||
| private readonly ILogger logger; | ||
|
|
||
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="DurableClientFactory"/> class. | ||
| /// </summary> | ||
| /// <param name="defaultDurableClientOptions">Default Options to Build Durable Clients.</param> | ||
| /// <param name="orchestrationServiceFactory">The factory used to create orchestration service based on the configured storage provider.</param> | ||
| /// <param name="loggerFactory">The logger factory used for extension-specific logging and orchestration tracking.</param> | ||
| /// <param name="durableTaskOptions">The configuration options for this extension.</param> | ||
| /// <param name="messageSerializerSettingsFactory">The factory used to create <see cref="JsonSerializerSettings"/> for message settings.</param> | ||
| public DurableClientFactory( | ||
| IOptions<DurableClientOptions> defaultDurableClientOptions, | ||
| IOptions<DurableTaskOptions> durableTaskOptions, | ||
| IDurabilityProviderFactory orchestrationServiceFactory, | ||
| ILoggerFactory loggerFactory, | ||
| IMessageSerializerSettingsFactory messageSerializerSettingsFactory = null) | ||
| { | ||
| this.logger = loggerFactory.CreateLogger(DurableTaskExtension.LoggerCategoryName); | ||
|
|
||
| this.durabilityProviderFactory = orchestrationServiceFactory; | ||
| this.defaultDurableClientOptions = defaultDurableClientOptions.Value; | ||
| this.durableTaskOptions = durableTaskOptions?.Value ?? new DurableTaskOptions(); | ||
|
|
||
| this.MessageDataConverter = DurableTaskExtension.CreateMessageDataConverter(messageSerializerSettingsFactory); | ||
| this.TraceHelper = new EndToEndTraceHelper(this.logger, this.durableTaskOptions.Tracing.TraceReplayEvents); | ||
| } | ||
|
|
||
| internal MessagePayloadDataConverter MessageDataConverter { get; private set; } | ||
|
|
||
| internal EndToEndTraceHelper TraceHelper { get; private set; } | ||
|
|
||
| /// <summary> | ||
| /// Gets a <see cref="IDurableClient"/> using configuration from a <see cref="DurableClientOptions"/> instance. | ||
| /// </summary> | ||
| /// <param name="durableClientOptions">options containing the client configuration parameters.</param> | ||
| /// <returns>Returns a <see cref="IDurableClient"/> instance. The returned instance may be a cached instance.</returns> | ||
| public IDurableClient CreateClient(DurableClientOptions durableClientOptions) | ||
|
||
| { | ||
| if (durableClientOptions == null) | ||
| { | ||
| throw new ArgumentException("Please configure 'DurableClientOptions'"); | ||
| } | ||
|
|
||
| if (string.IsNullOrWhiteSpace(durableClientOptions.TaskHub)) | ||
ConnorMcMahon marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| throw new ArgumentException("Please provide value for 'TaskHub'"); | ||
| } | ||
|
|
||
| DurableClientAttribute attribute = new DurableClientAttribute(durableClientOptions); | ||
|
|
||
| HttpApiHandler httpApiHandler = this.cachedHttpListeners.GetOrAdd( | ||
| attribute, | ||
| attr => | ||
| { | ||
| return new HttpApiHandler(null, null, this.durableTaskOptions, this.logger); | ||
| }); | ||
|
|
||
| DurableClient client = this.cachedClients.GetOrAdd( | ||
| attribute, | ||
| attr => | ||
| { | ||
| DurabilityProvider innerClient = this.durabilityProviderFactory.GetDurabilityProvider(attribute); | ||
| return new DurableClient(innerClient, httpApiHandler, attribute, this.MessageDataConverter, this.TraceHelper, this.durableTaskOptions); | ||
| }); | ||
|
|
||
| return client; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Gets a <see cref="IDurableClient"/> using configuration from a <see cref="DurableClientOptions"/> instance. | ||
| /// </summary> | ||
| /// <returns>Returns a <see cref="IDurableClient"/> instance. The returned instance may be a cached instance.</returns> | ||
| public IDurableClient CreateClient() | ||
| { | ||
| return this.CreateClient(this.defaultDurableClientOptions); | ||
| } | ||
|
|
||
| /// <inheritdoc /> | ||
| public void Dispose() | ||
| { | ||
| foreach (var cachedHttpListener in this.cachedHttpListeners) | ||
| { | ||
| cachedHttpListener.Value?.Dispose(); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| // Copyright (c) .NET Foundation. All rights reserved. | ||
| // Licensed under the MIT License. See LICENSE in the project root for license information. | ||
|
|
||
| using Microsoft.Azure.WebJobs.Extensions.DurableTask.Options; | ||
|
|
||
| namespace Microsoft.Azure.WebJobs.Extensions.DurableTask.ContextImplementations | ||
| { | ||
| /// <summary> | ||
| /// Factory class to create Durable Client to start works outside an azure function context. | ||
| /// </summary> | ||
| public interface IDurableClientFactory | ||
| { | ||
| /// <summary> | ||
| /// Gets a <see cref="IDurableClient"/> using configuration from a <see cref="DurableClientOptions"/> instance. | ||
| /// </summary> | ||
| /// <param name="durableClientOptions">options containing the client configuration parameters.</param> | ||
| /// <returns>Returns a <see cref="IDurableClient"/> instance. The returned instance may be a cached instance.</returns> | ||
| IDurableClient CreateClient(DurableClientOptions durableClientOptions); | ||
|
|
||
| /// <summary> | ||
| /// Gets a <see cref="IDurableClient"/> using configuration from a <see cref="DurableClientOptions"/> instance. | ||
| /// </summary> | ||
| /// <returns>Returns a <see cref="IDurableClient"/> instance. The returned instance may be a cached instance.</returns> | ||
| IDurableClient CreateClient(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,8 @@ | |
| using System.Net.Http; | ||
| using System.Threading; | ||
| #if !FUNCTIONS_V1 | ||
| using Microsoft.Azure.WebJobs.Extensions.DurableTask.ContextImplementations; | ||
| using Microsoft.Azure.WebJobs.Extensions.DurableTask.Options; | ||
| using Microsoft.Extensions.DependencyInjection; | ||
| using Microsoft.Extensions.DependencyInjection.Extensions; | ||
| using Microsoft.Extensions.Hosting; | ||
|
|
@@ -43,10 +45,45 @@ public static IWebJobsBuilder AddDurableTask(this IWebJobsBuilder builder) | |
| serviceCollection.TryAddSingleton<IMessageSerializerSettingsFactory, MessageSerializerSettingsFactory>(); | ||
| serviceCollection.TryAddSingleton<IErrorSerializerSettingsFactory, ErrorSerializerSettingsFactory>(); | ||
| serviceCollection.TryAddSingleton<IApplicationLifetimeWrapper, HostLifecycleService>(); | ||
| serviceCollection.TryAddSingleton<IDurableClientFactory, DurableClientFactory>(); | ||
ConnorMcMahon marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| return builder; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Adds the Durable Task extension to the provided <see cref="IServiceCollection"/>. | ||
| /// </summary> | ||
| /// <param name="serviceCollection">The <see cref="IServiceCollection"/> to configure.</param> | ||
| /// <returns>Returns the provided <see cref="IServiceCollection"/>.</returns> | ||
| public static IServiceCollection AddDurableTask(this IServiceCollection serviceCollection) | ||
| { | ||
| if (serviceCollection == null) | ||
| { | ||
| throw new ArgumentNullException(nameof(serviceCollection)); | ||
| } | ||
|
|
||
| serviceCollection.TryAddSingleton<INameResolver, DefaultNameResolver>(); | ||
| serviceCollection.TryAddSingleton<IConnectionStringResolver, StandardConnectionStringProvider>(); | ||
| serviceCollection.TryAddSingleton<IDurabilityProviderFactory, AzureStorageDurabilityProviderFactory>(); | ||
| serviceCollection.TryAddSingleton<IDurableClientFactory, DurableClientFactory>(); | ||
| serviceCollection.TryAddSingleton<IMessageSerializerSettingsFactory, MessageSerializerSettingsFactory>(); | ||
|
|
||
| return serviceCollection; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Adds the Durable Task extension to the provided <see cref="IServiceCollection"/>. | ||
| /// </summary> | ||
| /// <param name="serviceCollection">The <see cref="IServiceCollection"/> to configure.</param> | ||
| /// <param name="optionsBuilder">Populate default configurations of <see cref="DurableClientOptions"/> to create Durable Clients.</param> | ||
| /// <returns>Returns the provided <see cref="IServiceCollection"/>.</returns> | ||
| public static IServiceCollection AddDurableTask(this IServiceCollection serviceCollection, Action<DurableClientOptions> optionsBuilder) | ||
|
||
| { | ||
| AddDurableTask(serviceCollection); | ||
| serviceCollection.Configure<DurableClientOptions>(optionsBuilder.Invoke); | ||
| return serviceCollection; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Adds the Durable Task extension to the provided <see cref="IWebJobsBuilder"/>. | ||
| /// </summary> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cgillum, is there any reason for us to have this check at all? A
DurableClientcan be created with a different task hub already, and there is no guarantee that the app that is calling thisDurableClientwill have the same functions as the app with the TaskHub the client is referencing.