Created
May 3, 2024 00:14
-
-
Save ElanHasson/c8c2bcc59cce1157a407b5a2f3404c70 to your computer and use it in GitHub Desktop.
Keyed Temporal Clients and Workers
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.Extensions.DependencyInjection.Extensions; | |
using Microsoft.Extensions.Hosting; | |
using Microsoft.Extensions.Logging; | |
using Microsoft.Extensions.Options; | |
using Temporalio.Client; | |
using Temporalio.Extensions.Hosting; | |
namespace Microsoft.Extensions.DependencyInjection; | |
/// <summary> | |
/// Temporal extensions for working with keyed entries in <see cref="IServiceCollection" />. | |
/// </summary> | |
public static class TemporalKeyedHostingServiceCollectionExtensions | |
{ | |
/// <summary> | |
/// Add a hosted Temporal worker service as a <see cref="IHostedService" /> that contains | |
/// its own client that connects with the given target and namespace. To use an injected | |
/// <see cref="ITemporalClient" />, use | |
/// <see cref="AddKeyedHostedTemporalWorker(IServiceCollection, string)" />. The worker service | |
/// will be registered as a singleton. The result is an options builder that can be used to | |
/// configure the service. | |
/// </summary> | |
/// <param name="services">Service collection to create hosted worker on.</param> | |
/// <param name="serviceKey"><see cref="ServiceDescriptor.ServiceKey" /> of the service.</param> | |
/// <param name="clientTargetHost">Client target host to connect to when starting the | |
/// worker.</param> | |
/// <param name="clientNamespace">Client namespace to connect to when starting the worker. | |
/// </param> | |
/// <param name="taskQueue">Task queue for the worker.</param> | |
/// <returns>Options builder to configure the service.</returns> | |
public static ITemporalWorkerServiceOptionsBuilder AddKeyedHostedTemporalWorker( | |
this IServiceCollection services, | |
object serviceKey, | |
string clientTargetHost, | |
string clientNamespace, | |
string taskQueue) => | |
services.AddKeyedHostedTemporalWorker(serviceKey, taskQueue).ConfigureOptions(options => | |
options.ClientOptions = new(clientTargetHost) { Namespace = clientNamespace }); | |
/// <summary> | |
/// Add a hosted Temporal worker service as a <see cref="IHostedService" /> that expects | |
/// an injected <see cref="ITemporalClient" /> (or the returned builder | |
/// can have client options populated). Use | |
/// <see cref="AddKeyedHostedTemporalWorker(IServiceCollection, string, string, string)" /> to | |
/// not expect an injected instance and instead connect to a client on worker start. The | |
/// worker service will be registered as a singleton. The result is an options builder that | |
/// can be used to configure the service. | |
/// </summary> | |
/// <param name="serviceKey"><see cref="ServiceDescriptor.ServiceKey" /> of the service.</param> | |
/// <param name="services">Service collection to create hosted worker on.</param> | |
/// <param name="taskQueue">Task queue for the worker.</param> | |
/// <returns>Options builder to configure the service.</returns> | |
public static ITemporalWorkerServiceOptionsBuilder AddKeyedHostedTemporalWorker( | |
this IServiceCollection services, object serviceKey, string taskQueue) | |
{ | |
// We have to use AddSingleton instead of AddHostedService because the latter does | |
// not allow us to register multiple of the same type, see | |
// https://github.com/dotnet/runtime/issues/38751 | |
services.AddKeyedSingleton<IHostedService>(serviceKey, (provider, k) => | |
ActivatorUtilities.CreateInstance<TemporalWorkerService>(provider, taskQueue)); | |
return new TemporalWorkerServiceOptionsBuilder(taskQueue, services).ConfigureOptions( | |
options => options.TaskQueue = taskQueue); | |
} | |
/// <summary> | |
/// Adds a singleton <see cref="ITemporalClient" /> via | |
/// <see cref="ServiceCollectionDescriptorExtensions.TryAddKeyedSingleton{TService}(IServiceCollection, object?, Func{IServiceProvider, object, TService})" /> | |
/// using a lazy client created with <see cref="TemporalClient.CreateLazy" />. The resulting | |
/// builder can be used to configure the client as can any options approach that alters | |
/// <see cref="TemporalClientConnectOptions" />. If a logging factory is on the container, | |
/// it will be set on the client. | |
/// </summary> | |
/// <param name="services">Service collection to add Temporal client to.</param> | |
/// <param name="serviceKey"><see cref="ServiceDescriptor.ServiceKey" /> of the service.</param> | |
/// <param name="clientTargetHost">If set, the host to connect to.</param> | |
/// <param name="clientNamespace">If set, the namespace for the client.</param> | |
/// <returns>Options builder for setting client options.</returns> | |
/// <remarks> | |
/// This client can be used with <see cref="TemporalWorkerService" /> from | |
/// <c>AddKeyedHostedTemporalWorker</c> but not <see cref="Temporalio.Worker.TemporalWorker" /> | |
/// directly because lazy unconnected clients can't be used directly with those workers. | |
/// </remarks> | |
public static OptionsBuilder<TemporalClientConnectOptions> AddKeyedTemporalClient( | |
this IServiceCollection services, | |
object serviceKey, | |
string? clientTargetHost = null, | |
string? clientNamespace = null) | |
{ | |
services.TryAddKeyedSingleton<ITemporalClient>(serviceKey, (provider, k) => | |
{ | |
var options = provider.GetRequiredService<IOptionsMonitor<TemporalClientConnectOptions>>().Get(serviceKey.GetType().FullName); | |
return TemporalClient.CreateLazy(options); | |
}); | |
var builder = services.AddOptions<TemporalClientConnectOptions>(serviceKey.GetType().FullName); | |
if (clientTargetHost != null || clientNamespace != null) | |
{ | |
builder.Configure(options => | |
{ | |
options.TargetHost = clientTargetHost; | |
if (clientNamespace != null) | |
{ | |
options.Namespace = clientNamespace; | |
} | |
}); | |
} | |
builder.Configure<IServiceProvider>((options, provider) => | |
{ | |
if (provider.GetService<ILoggerFactory>() is { } loggerFactory) | |
{ | |
options.LoggerFactory = loggerFactory; | |
} | |
}); | |
return builder; | |
} | |
/// <summary> | |
/// Adds a singleton <see cref="ITemporalClient" /> via | |
/// <see cref="ServiceCollectionDescriptorExtensions.TryAddKeyedSingleton{TService}(IServiceCollection, object?, Func{IServiceProvider, object, TService})" /> | |
/// using a lazy client created with <see cref="TemporalClient.CreateLazy" />. The action | |
/// can be used to configure the client as can any options approach that alters | |
/// <see cref="TemporalClientConnectOptions" />. If a logging factory is on the container, | |
/// it will be set on the client. | |
/// </summary> | |
/// <param name="services">Service collection to add Temporal client to.</param> | |
/// <param name="serviceKey"><see cref="ServiceDescriptor.ServiceKey" /> of the service.</param> | |
/// <param name="configureClient">Action to configure client options.</param> | |
/// <returns>The given service collection for chaining.</returns> | |
public static IServiceCollection AddKeyedTemporalClient( | |
this IServiceCollection services, object serviceKey, Action<TemporalClientConnectOptions> configureClient) | |
{ | |
services.AddKeyedTemporalClient(serviceKey).Configure(configureClient); | |
return services; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
hah, no idea why we would need keyed workers lol, but here they are :)