Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save ElanHasson/c8c2bcc59cce1157a407b5a2f3404c70 to your computer and use it in GitHub Desktop.
Save ElanHasson/c8c2bcc59cce1157a407b5a2f3404c70 to your computer and use it in GitHub Desktop.
Keyed Temporal Clients and Workers
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;
}
}
@ElanHasson
Copy link
Author

hah, no idea why we would need keyed workers lol, but here they are :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment