Skip to content

Commit

Permalink
Add new create builder overloads without callbacks (#366)
Browse files Browse the repository at this point in the history
* Add new builder overload without callback

* Add unit tests

* Fix TaskNameTests namsespace

* Update changelog

* Fix tests passing null
  • Loading branch information
jviau authored Jan 24, 2025
1 parent fd86ae1 commit 2ff37c5
Show file tree
Hide file tree
Showing 14 changed files with 379 additions and 89 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changelog

## vNext

### Microsoft.DurableTask.Client

- Add new `IDurableTaskClientBuilder AddDurableTaskClient(IServiceCollection, string?)` API

### Microsoft.DurableTask.Worker

- Add new `IDurableTaskWorkerBuilder AddDurableTaskWorker(IServiceCollection, string?)` API

## v1.5.0

- Implement work item completion tokens for standalone worker scenarios ([#359](https://github.com/microsoft/durabletask-dotnet/pull/359))
Expand Down
7 changes: 7 additions & 0 deletions Microsoft.DurableTask.sln
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{CECADD
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shared.AzureManaged.Tests", "test\Shared\AzureManaged.Tests\Shared.AzureManaged.Tests.csproj", "{3272C041-F81D-4C85-A4FB-2A700B5A7A9D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleAppMinimal", "samples\ConsoleAppMinimal\ConsoleAppMinimal.csproj", "{B48FACA9-A328-452A-BFAE-C4F60F9C7024}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -217,6 +219,10 @@ Global
{3272C041-F81D-4C85-A4FB-2A700B5A7A9D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3272C041-F81D-4C85-A4FB-2A700B5A7A9D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3272C041-F81D-4C85-A4FB-2A700B5A7A9D}.Release|Any CPU.Build.0 = Release|Any CPU
{B48FACA9-A328-452A-BFAE-C4F60F9C7024}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B48FACA9-A328-452A-BFAE-C4F60F9C7024}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B48FACA9-A328-452A-BFAE-C4F60F9C7024}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B48FACA9-A328-452A-BFAE-C4F60F9C7024}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -258,6 +264,7 @@ Global
{1E5C2E83-7B6B-425A-9C9B-0B887D273B12} = {51DC98A3-0193-4C66-964B-C26C748E25B6}
{CECADDB5-E30A-4CE2-8604-9AC596D4A2DC} = {E5637F81-2FB9-4CD7-900D-455363B142A7}
{3272C041-F81D-4C85-A4FB-2A700B5A7A9D} = {CECADDB5-E30A-4CE2-8604-9AC596D4A2DC}
{B48FACA9-A328-452A-BFAE-C4F60F9C7024} = {EFF7632B-821E-4CFC-B4A0-ED4B24296B17}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {AB41CB55-35EA-4986-A522-387AB3402E71}
Expand Down
18 changes: 14 additions & 4 deletions samples/ConsoleApp/ConsoleApp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,24 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.0" />
<PackageReference Include="Microsoft.DurableTask.Client.Grpc" Version="1.1.0" />
<PackageReference Include="Microsoft.DurableTask.Worker.Grpc" Version="1.1.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />

<!-- Real projects would use package references -->
<!--
<PackageReference Include="Microsoft.DurableTask.Client.Grpc" Version="1.5.0" />
<PackageReference Include="Microsoft.DurableTask.Worker.Grpc" Version="1.5.0" />
-->
</ItemGroup>

<ItemGroup>
<!-- Using p2p references so we can show latest changes in samples. -->
<ProjectReference Include="$(SrcRoot)Client/Grpc/Client.Grpc.csproj" />
<ProjectReference Include="$(SrcRoot)Worker/Grpc/Worker.Grpc.csproj" />
</ItemGroup>

</Project>
93 changes: 40 additions & 53 deletions samples/ConsoleApp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,68 +8,55 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddDurableTaskClient(builder =>
{
// Configure options for this builder. Can be omitted if no options customization is needed.
builder.Configure(opt => { });
builder.UseGrpc(); // multiple overloads available for providing gRPC information
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

// AddDurableTaskClient allows for multiple named clients by passing in a name as the first argument.
// When using a non-default named client, you will need to make this call below to have the
// DurableTaskClient added directly to the DI container. Otherwise IDurableTaskClientProvider must be used
// to retrieve DurableTaskClients by name from the DI container. In this case, we are using the default
// name, so the line below is NOT required as it was already called for us.
builder.RegisterDirectly();
});
IDurableTaskClientBuilder clientBuilder = builder.Services.AddDurableTaskClient()
.Configure(opt => { }) // configure options for this builder, if desired.
.UseGrpc(); // multiple overloads available for providing gRPC information

services.AddDurableTaskWorker(builder =>
{
// Configure options for this builder. Can be omitted if no options customization is needed.
builder.Configure(opt => { });
// OPTIONAL STEP
// AddDurableTaskClient allows for multiple named clients by passing in a name as the first argument.
// When using a non-default named client, you will need to make this call below to have the
// DurableTaskClient added directly to the DI container. Otherwise IDurableTaskClientProvider must be used
// to retrieve DurableTaskClients by name from the DI container. In this case, we are using the default
// name, so the line below is NOT required as it was already called for us.
clientBuilder.RegisterDirectly();

// Register orchestrators and activities.
builder.AddTasks(tasks =>
builder.Services.AddDurableTaskWorker()
.Configure(opt => { }) // configure options for this builder.
.AddTasks(tasks =>
{
// Add tasks to the worker.
tasks.AddOrchestratorFunc("HelloSequence", async context =>
{
var greetings = new List<string>
{
tasks.AddOrchestratorFunc("HelloSequence", async context =>
{
var greetings = new List<string>
{
await context.CallActivityAsync<string>("SayHello", "Tokyo"),
await context.CallActivityAsync<string>("SayHello", "London"),
await context.CallActivityAsync<string>("SayHello", "Seattle"),
};

return greetings;
});

tasks.AddActivityFunc<string, string>("SayHello", (context, city) => $"Hello {city}!");
});
await context.CallActivityAsync<string>("SayHello", "Tokyo"),
await context.CallActivityAsync<string>("SayHello", "London"),
await context.CallActivityAsync<string>("SayHello", "Seattle"),
};

builder.UseGrpc(); // multiple overloads available for providing gRPC information
return greetings;
});

// Can also configure worker and client options through all the existing options config methods.
// These are equivalent to the 'builder.Configure' calls above.
services.Configure<DurableTaskWorkerOptions>(opt => { });
services.Configure<DurableTaskClientOptions>(opt => { });
tasks.AddActivityFunc<string, string>("SayHello", (context, city) => $"Hello {city}!");
})
.UseGrpc(); // multiple overloads available for providing gRPC information

// Registry can also be done via options pattern. This is equivalent to the 'builder.AddTasks' call above.
// You can use all the tools options pattern has available. For example, if you have multiple workers you could
// use ConfigureAll<DurableTaskRegistry> to add tasks to ALL workers in one go. Otherwise, you need to use
// named option configuration to register to specific workers (where the name matches the name passed to
// 'AddDurableTaskWorker(name?, builder)').
services.Configure<DurableTaskRegistry>(registry => { });
// OPTIONAL STEP
// Client and Worker options can also be configured through the options pattern.
// When using the options pattern, configure with the same name as the builder.
builder.Services.Configure<DurableTaskClientOptions>(opt => { });
builder.Services.Configure<DurableTaskWorkerOptions>(opt => { });
builder.Services.Configure<DurableTaskRegistry>(registry => { });

// You can configure custom data converter multiple ways. One is through worker/client options configuration.
// Alternatively, data converter will be used from the service provider if available (as a singleton) AND no
// converter was explicitly set on the options.
services.AddSingleton<DataConverter>(JsonDataConverter.Default);
})
.Build();
// OPTIONAL STEP
// You can configure custom data converter multiple ways. One is through worker/client options configuration.
// Alternatively, data converter will be used from the service provider if available (as a singleton) AND no
// converter was explicitly set on the options.
builder.Services.AddSingleton<DataConverter>(JsonDataConverter.Default);

IHost host = builder.Build();
await host.StartAsync();

await using DurableTaskClient client = host.Services.GetRequiredService<DurableTaskClient>();
Expand All @@ -82,4 +69,4 @@ await context.CallActivityAsync<string>("SayHello", "Seattle"),
getInputsAndOutputs: true,
cts.Token);

Console.WriteLine($"Instance completed: {instance}");
Console.WriteLine($"Instance completed: {instance}");
25 changes: 25 additions & 0 deletions samples/ConsoleAppMinimal/ConsoleAppMinimal.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />

<!-- Real projects would use package references -->
<!--
<PackageReference Include="Microsoft.DurableTask.Client.Grpc" Version="1.5.0" />
<PackageReference Include="Microsoft.DurableTask.Worker.Grpc" Version="1.5.0" />
-->
</ItemGroup>

<ItemGroup>
<!-- Using p2p references so we can show latest changes in samples. -->
<ProjectReference Include="$(SrcRoot)Client/Grpc/Client.Grpc.csproj" />
<ProjectReference Include="$(SrcRoot)Worker/Grpc/Worker.Grpc.csproj" />
</ItemGroup>

</Project>
23 changes: 23 additions & 0 deletions samples/ConsoleAppMinimal/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

// This app differs from samples/ConsoleApp in that we show the absolute minimum code needed to run a Durable Task application.

using ConsoleAppMinimal;
using Microsoft.DurableTask.Client;
using Microsoft.DurableTask.Worker;
using Microsoft.Extensions.Hosting;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddDurableTaskClient().UseGrpc();
builder.Services.AddDurableTaskWorker()
.AddTasks(tasks =>
{
tasks.AddOrchestrator<HelloSequenceOrchestrator>();
tasks.AddActivity<SayHelloActivity>();
})
.UseGrpc();

IHost host = builder.Build();
await host.StartAsync();
31 changes: 31 additions & 0 deletions samples/ConsoleAppMinimal/Tasks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Microsoft.DurableTask;

namespace ConsoleAppMinimal;

[DurableTask("HelloSequence")]
public class HelloSequenceOrchestrator : TaskOrchestrator<string, IEnumerable<string>>
{
public override async Task<IEnumerable<string>> RunAsync(TaskOrchestrationContext context, string input)
{
IEnumerable<string> greetings =
[
await context.CallActivityAsync<string>("SayHello", "Tokyo"),
await context.CallActivityAsync<string>("SayHello", "London"),
await context.CallActivityAsync<string>("SayHello", "Seattle"),
];

return greetings;
}
}

[DurableTask("SayHello")]
public class SayHelloActivity : TaskActivity<string, string>
{
public override Task<string> RunAsync(TaskActivityContext context, string city)
{
return Task.FromResult($"Hello {city}!");
}
}
48 changes: 34 additions & 14 deletions src/Client/Core/DependencyInjection/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,20 @@ namespace Microsoft.DurableTask.Client;
/// </summary>
public static class ServiceCollectionExtensions
{
/// <summary>
/// Adds and configures Durable Task worker services to the service collection.
/// </summary>
/// <param name="services">The service collection to add to.</param>
/// <param name="name">The name of the builder to add.</param>
/// <returns>The builder used to configured the <see cref="DurableTaskClient"/>.</returns>
public static IDurableTaskClientBuilder AddDurableTaskClient(this IServiceCollection services, string? name = null)
{
Check.NotNull(services);
IDurableTaskClientBuilder builder = GetBuilder(services, name ?? Options.DefaultName, out bool added);
ConditionalConfigureBuilder(services, builder, added);
return builder;
}

/// <summary>
/// Configures and adds a <see cref="DurableTaskClient" /> to the service collection.
/// </summary>
Expand All @@ -37,25 +51,31 @@ public static IServiceCollection AddDurableTaskClient(
services.TryAddSingleton<IDurableTaskClientProvider, DefaultDurableTaskClientProvider>();
IDurableTaskClientBuilder builder = GetBuilder(services, name, out bool added);
configure.Invoke(builder);
ConditionalConfigureBuilder(services, builder, added);
return services;
}

if (added)
static void ConditionalConfigureBuilder(
IServiceCollection services, IDurableTaskClientBuilder builder, bool configure)
{
if (!configure)
{
// The added toggle logic is because we cannot use TryAddEnumerable logic as
// we would have to dynamically compile a lambda to have it work correctly.
ConfigureDurableOptions(services, name);
return;
}

// We do not want to register DurableTaskClient type directly so we can keep a max of 1 DurableTaskClients
// registered, allowing for direct-DI of the default client.
services.AddSingleton(sp => new DefaultDurableTaskClientProvider.ClientContainer(builder.Build(sp)));
// The added toggle logic is because we cannot use TryAddEnumerable logic as
// we would have to dynamically compile a lambda to have it work correctly.
ConfigureDurableOptions(services, builder.Name);

if (name == Options.DefaultName)
{
// If we have the default options name here, we will inject this client directly.
builder.RegisterDirectly();
}
}
// We do not want to register DurableTaskClient type directly so we can keep a max of 1 DurableTaskClients
// registered, allowing for direct-DI of the default client.
services.AddSingleton(sp => new DefaultDurableTaskClientProvider.ClientContainer(builder.Build(sp)));

return services;
if (builder.Name == Options.DefaultName)
{
// If we have the default options name here, we will inject this client directly.
builder.RegisterDirectly();
}
}

static IServiceCollection ConfigureDurableOptions(IServiceCollection services, string name)
Expand Down
2 changes: 1 addition & 1 deletion src/Client/Core/RELEASENOTES.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
- Fix filter not being passed along in `PurgeAllInstancesAsync` (https://github.com/microsoft/durabletask-dotnet/pull/289)
- Add new `IDurableTaskClientBuilder AddDurableTaskClient(IServiceCollection, string?)` API
Loading

0 comments on commit 2ff37c5

Please sign in to comment.