Skip to content
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

Add new create builder overloads without callbacks #366

Merged
merged 5 commits into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading