Skip to content

Commit 3e37085

Browse files
authored
Implement per-orchestration and per-activity versioning (#695)
1 parent 0162a66 commit 3e37085

53 files changed

Lines changed: 5963 additions & 382 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,7 @@ MigrationBackup/
351351

352352
# Rider (cross platform .NET/C# tools) working folder
353353
.idea/
354+
.worktrees/
354355
AzuriteConfig
355356
__azurite_db_*
356357
__blobstorage__

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
- Add per-task versioning via `[DurableTask(Version = "...")]`, `TaskOptions.Version`, and `StartOrchestrationOptions.Version` ([#695](https://github.com/microsoft/durabletask-dotnet/pull/695))
6+
57

68
## v1.24.2
79
- Bump DI.Abstractions and Bcl.AsyncInterfaces to 9.0.1 ([#3433](https://github.com/microsoft/durabletask-dotnet/pull/3433)) (#723)

Microsoft.DurableTask.sln

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
1+
22
Microsoft Visual Studio Solution File, Format Version 12.00
33
# Visual Studio Version 17
44
VisualStudioVersion = 17.3.32901.215
@@ -115,6 +115,18 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NamespaceGenerationSample",
115115
EndProject
116116
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReplaySafeLoggerFactorySample", "samples\ReplaySafeLoggerFactorySample\ReplaySafeLoggerFactorySample.csproj", "{8E7BECBC-7226-4778-B8F2-8EBDFF0D3BA4}"
117117
EndProject
118+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkerVersioningSample", "samples\WorkerVersioningSample\WorkerVersioningSample.csproj", "{26988639-D204-4E0B-80BE-F4E11952DFF8}"
119+
EndProject
120+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AzureManaged", "AzureManaged", "{D4587EC0-1B16-8420-7502-A967139249D4}"
121+
EndProject
122+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AzureManaged", "AzureManaged", "{53193780-CD18-2643-6953-C26F59EAEDF5}"
123+
EndProject
124+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EternalOrchestrationVersionMigrationSample", "samples\EternalOrchestrationVersionMigrationSample\EternalOrchestrationVersionMigrationSample.csproj", "{1E30F09F-1ADA-4375-81CC-F0FBC74D5621}"
125+
EndProject
126+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ActivityVersioningSample", "samples\ActivityVersioningSample\ActivityVersioningSample.csproj", "{3FBCFDBA-F547-4FD5-B8C6-0B645EF73E3A}"
127+
EndProject
128+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EntityWithVersionedOrchestrationSample", "samples\EntityWithVersionedOrchestrationSample\EntityWithVersionedOrchestrationSample.csproj", "{8E0D27B3-2B5D-4B6F-A4E6-5C8E7B0F7DD2}"
129+
EndProject
118130
Global
119131
GlobalSection(SolutionConfigurationPlatforms) = preSolution
120132
Debug|Any CPU = Debug|Any CPU
@@ -701,7 +713,54 @@ Global
701713
{8E7BECBC-7226-4778-B8F2-8EBDFF0D3BA4}.Release|x64.Build.0 = Release|Any CPU
702714
{8E7BECBC-7226-4778-B8F2-8EBDFF0D3BA4}.Release|x86.ActiveCfg = Release|Any CPU
703715
{8E7BECBC-7226-4778-B8F2-8EBDFF0D3BA4}.Release|x86.Build.0 = Release|Any CPU
704-
716+
{26988639-D204-4E0B-80BE-F4E11952DFF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
717+
{26988639-D204-4E0B-80BE-F4E11952DFF8}.Debug|Any CPU.Build.0 = Debug|Any CPU
718+
{26988639-D204-4E0B-80BE-F4E11952DFF8}.Debug|x64.ActiveCfg = Debug|Any CPU
719+
{26988639-D204-4E0B-80BE-F4E11952DFF8}.Debug|x64.Build.0 = Debug|Any CPU
720+
{26988639-D204-4E0B-80BE-F4E11952DFF8}.Debug|x86.ActiveCfg = Debug|Any CPU
721+
{26988639-D204-4E0B-80BE-F4E11952DFF8}.Debug|x86.Build.0 = Debug|Any CPU
722+
{26988639-D204-4E0B-80BE-F4E11952DFF8}.Release|Any CPU.ActiveCfg = Release|Any CPU
723+
{26988639-D204-4E0B-80BE-F4E11952DFF8}.Release|Any CPU.Build.0 = Release|Any CPU
724+
{26988639-D204-4E0B-80BE-F4E11952DFF8}.Release|x64.ActiveCfg = Release|Any CPU
725+
{26988639-D204-4E0B-80BE-F4E11952DFF8}.Release|x64.Build.0 = Release|Any CPU
726+
{26988639-D204-4E0B-80BE-F4E11952DFF8}.Release|x86.ActiveCfg = Release|Any CPU
727+
{26988639-D204-4E0B-80BE-F4E11952DFF8}.Release|x86.Build.0 = Release|Any CPU
728+
{1E30F09F-1ADA-4375-81CC-F0FBC74D5621}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
729+
{1E30F09F-1ADA-4375-81CC-F0FBC74D5621}.Debug|Any CPU.Build.0 = Debug|Any CPU
730+
{1E30F09F-1ADA-4375-81CC-F0FBC74D5621}.Debug|x64.ActiveCfg = Debug|Any CPU
731+
{1E30F09F-1ADA-4375-81CC-F0FBC74D5621}.Debug|x64.Build.0 = Debug|Any CPU
732+
{1E30F09F-1ADA-4375-81CC-F0FBC74D5621}.Debug|x86.ActiveCfg = Debug|Any CPU
733+
{1E30F09F-1ADA-4375-81CC-F0FBC74D5621}.Debug|x86.Build.0 = Debug|Any CPU
734+
{1E30F09F-1ADA-4375-81CC-F0FBC74D5621}.Release|Any CPU.ActiveCfg = Release|Any CPU
735+
{1E30F09F-1ADA-4375-81CC-F0FBC74D5621}.Release|Any CPU.Build.0 = Release|Any CPU
736+
{1E30F09F-1ADA-4375-81CC-F0FBC74D5621}.Release|x64.ActiveCfg = Release|Any CPU
737+
{1E30F09F-1ADA-4375-81CC-F0FBC74D5621}.Release|x64.Build.0 = Release|Any CPU
738+
{1E30F09F-1ADA-4375-81CC-F0FBC74D5621}.Release|x86.ActiveCfg = Release|Any CPU
739+
{1E30F09F-1ADA-4375-81CC-F0FBC74D5621}.Release|x86.Build.0 = Release|Any CPU
740+
{3FBCFDBA-F547-4FD5-B8C6-0B645EF73E3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
741+
{3FBCFDBA-F547-4FD5-B8C6-0B645EF73E3A}.Debug|Any CPU.Build.0 = Debug|Any CPU
742+
{3FBCFDBA-F547-4FD5-B8C6-0B645EF73E3A}.Debug|x64.ActiveCfg = Debug|Any CPU
743+
{3FBCFDBA-F547-4FD5-B8C6-0B645EF73E3A}.Debug|x64.Build.0 = Debug|Any CPU
744+
{3FBCFDBA-F547-4FD5-B8C6-0B645EF73E3A}.Debug|x86.ActiveCfg = Debug|Any CPU
745+
{3FBCFDBA-F547-4FD5-B8C6-0B645EF73E3A}.Debug|x86.Build.0 = Debug|Any CPU
746+
{3FBCFDBA-F547-4FD5-B8C6-0B645EF73E3A}.Release|Any CPU.ActiveCfg = Release|Any CPU
747+
{3FBCFDBA-F547-4FD5-B8C6-0B645EF73E3A}.Release|Any CPU.Build.0 = Release|Any CPU
748+
{3FBCFDBA-F547-4FD5-B8C6-0B645EF73E3A}.Release|x64.ActiveCfg = Release|Any CPU
749+
{3FBCFDBA-F547-4FD5-B8C6-0B645EF73E3A}.Release|x64.Build.0 = Release|Any CPU
750+
{3FBCFDBA-F547-4FD5-B8C6-0B645EF73E3A}.Release|x86.ActiveCfg = Release|Any CPU
751+
{3FBCFDBA-F547-4FD5-B8C6-0B645EF73E3A}.Release|x86.Build.0 = Release|Any CPU
752+
{8E0D27B3-2B5D-4B6F-A4E6-5C8E7B0F7DD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
753+
{8E0D27B3-2B5D-4B6F-A4E6-5C8E7B0F7DD2}.Debug|Any CPU.Build.0 = Debug|Any CPU
754+
{8E0D27B3-2B5D-4B6F-A4E6-5C8E7B0F7DD2}.Debug|x64.ActiveCfg = Debug|Any CPU
755+
{8E0D27B3-2B5D-4B6F-A4E6-5C8E7B0F7DD2}.Debug|x64.Build.0 = Debug|Any CPU
756+
{8E0D27B3-2B5D-4B6F-A4E6-5C8E7B0F7DD2}.Debug|x86.ActiveCfg = Debug|Any CPU
757+
{8E0D27B3-2B5D-4B6F-A4E6-5C8E7B0F7DD2}.Debug|x86.Build.0 = Debug|Any CPU
758+
{8E0D27B3-2B5D-4B6F-A4E6-5C8E7B0F7DD2}.Release|Any CPU.ActiveCfg = Release|Any CPU
759+
{8E0D27B3-2B5D-4B6F-A4E6-5C8E7B0F7DD2}.Release|Any CPU.Build.0 = Release|Any CPU
760+
{8E0D27B3-2B5D-4B6F-A4E6-5C8E7B0F7DD2}.Release|x64.ActiveCfg = Release|Any CPU
761+
{8E0D27B3-2B5D-4B6F-A4E6-5C8E7B0F7DD2}.Release|x64.Build.0 = Release|Any CPU
762+
{8E0D27B3-2B5D-4B6F-A4E6-5C8E7B0F7DD2}.Release|x86.ActiveCfg = Release|Any CPU
763+
{8E0D27B3-2B5D-4B6F-A4E6-5C8E7B0F7DD2}.Release|x86.Build.0 = Release|Any CPU
705764
EndGlobalSection
706765
GlobalSection(SolutionProperties) = preSolution
707766
HideSolutionNode = FALSE
@@ -759,7 +818,12 @@ Global
759818
{4A7305AE-AAAE-43AE-AAB2-DA58DACC6FA8} = {EFF7632B-821E-4CFC-B4A0-ED4B24296B17}
760819
{5A69FD28-D814-490E-A76B-B0A5F88C25B2} = {EFF7632B-821E-4CFC-B4A0-ED4B24296B17}
761820
{8E7BECBC-7226-4778-B8F2-8EBDFF0D3BA4} = {EFF7632B-821E-4CFC-B4A0-ED4B24296B17}
762-
821+
{26988639-D204-4E0B-80BE-F4E11952DFF8} = {EFF7632B-821E-4CFC-B4A0-ED4B24296B17}
822+
{D4587EC0-1B16-8420-7502-A967139249D4} = {1C217BB2-CE16-41CC-9D47-0FC0DB60BDB3}
823+
{53193780-CD18-2643-6953-C26F59EAEDF5} = {5B448FF6-EC42-491D-A22E-1DC8B618E6D5}
824+
{1E30F09F-1ADA-4375-81CC-F0FBC74D5621} = {EFF7632B-821E-4CFC-B4A0-ED4B24296B17}
825+
{3FBCFDBA-F547-4FD5-B8C6-0B645EF73E3A} = {EFF7632B-821E-4CFC-B4A0-ED4B24296B17}
826+
{8E0D27B3-2B5D-4B6F-A4E6-5C8E7B0F7DD2} = {EFF7632B-821E-4CFC-B4A0-ED4B24296B17}
763827
EndGlobalSection
764828
GlobalSection(ExtensibilityGlobals) = postSolution
765829
SolutionGuid = {AB41CB55-35EA-4986-A522-387AB3402E71}

README.md

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,15 +155,48 @@ public class SayHelloTyped : TaskActivity<string, string>
155155

156156
You can find the full sample file, including detailed comments, at [samples/AzureFunctionsApp/HelloCitiesTyped.cs](samples/AzureFunctionsApp/HelloCitiesTyped.cs).
157157

158+
### Versioned class-based orchestrators (standalone worker)
159+
160+
Standalone worker projects can register multiple class-based orchestrators under the same durable task name when each class declares a unique `[DurableTask(Version = "...")]`. Start a specific implementation by setting `StartOrchestrationOptions.Version`.
161+
162+
```csharp
163+
[DurableTask("OrderWorkflow", Version = "v1")]
164+
public sealed class OrderWorkflowV1 : TaskOrchestrator<int, string>
165+
{
166+
public override Task<string> RunAsync(TaskOrchestrationContext context, int input)
167+
=> Task.FromResult($"v1:{input}");
168+
}
169+
170+
[DurableTask("OrderWorkflow", Version = "v2")]
171+
public sealed class OrderWorkflowV2 : TaskOrchestrator<int, string>
172+
{
173+
public override Task<string> RunAsync(TaskOrchestrationContext context, int input)
174+
=> Task.FromResult($"v2:{input}");
175+
}
176+
177+
string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(
178+
"OrderWorkflow",
179+
input: 5,
180+
new StartOrchestrationOptions { Version = new TaskVersion("v2") });
181+
```
182+
183+
Use `ContinueAsNewOptions.NewVersion` to migrate long-running orchestrations at a replay-safe boundary.
184+
185+
> Per-class `[DurableTask(Version = "...")]` routing composes with `DurableTaskWorkerOptions.Versioning` (or `UseVersioning(...)`). The worker-level `MatchStrategy` gates which instance versions are accepted off the wire, and the registry then dispatches each accepted work item to the implementation that exactly matches its `(name, version)`. Use them together when a single worker needs to host multiple versions of the same name.
186+
>
187+
> Azure Functions projects do not support same-name multi-version class-based orchestrators in v1. The source generator reports a diagnostic instead of generating colliding triggers.
188+
158189
### Compatibility with Durable Functions in-process
159190

160191
This SDK is *not* compatible with Durable Functions for the .NET *in-process* worker. It only works with the newer out-of-process .NET Isolated worker.
161192

162-
## Usage with the Durable Task Scheduler
193+
## Usage with Durable Task Scheduler
194+
195+
Durable Task Scheduler provides durable execution in Azure. Durable execution is a fault-tolerant approach to running code that handles failures and interruptions through automatic retries and state persistence.
163196

164-
The Durable Task Scheduler for Azure Functions is a managed backend that is currently in preview. Durable Functions apps can use the Durable Task Scheduler as one of its [supported storage providers](https://learn.microsoft.com/azure/azure-functions/durable/durable-functions-storage-providers).
197+
This SDK can also be used with the Durable Task Scheduler directly, without any Durable Functions dependency. For getting started, you can find documentation and samples [here](https://learn.microsoft.com/en-us/azure/azure-functions/durable/what-is-durable-task).
165198

166-
This SDK can also be used with the Durable Task Scheduler directly, without any Durable Functions dependency. To get started, sign up for the [Durable Task Scheduler private preview](https://techcommunity.microsoft.com/blog/appsonazureblog/announcing-limited-early-access-of-the-durable-task-scheduler-for-azure-durable-/4286526) and follow the instructions to create a new Durable Task Scheduler instance. Once granted access to the private preview GitHub repository, you can find samples and documentation for getting started [here](https://github.com/Azure/Azure-Functions-Durable-Task-Scheduler-Private-Preview/tree/main/samples/portable-sdk/dotnet/AspNetWebApp#readme).
199+
For runnable DTS emulator examples that demonstrate versioning, see the [WorkerVersioningSample](samples/WorkerVersioningSample/README.md) (deployment-based versioning), the [EternalOrchestrationVersionMigrationSample](samples/EternalOrchestrationVersionMigrationSample/README.md) (multi-version routing with `[DurableTask(Version = "...")]`), the [ActivityVersioningSample](samples/ActivityVersioningSample/README.md) (activity versioning with inherited defaults and explicit override support), and the [EntityWithVersionedOrchestrationSample](samples/EntityWithVersionedOrchestrationSample/README.md) (a single instance migrating v1→v2 via `ContinueAsNew(NewVersion)` while preserving entity-held state).
167200

168201
## Obtaining the Protobuf definitions
169202

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net10.0</TargetFramework>
6+
<Nullable>enable</Nullable>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="Microsoft.Extensions.Hosting" />
11+
</ItemGroup>
12+
13+
<ItemGroup>
14+
<!-- Using p2p references so we can show latest changes in samples. -->
15+
<ProjectReference Include="$(SrcRoot)Client/AzureManaged/Client.AzureManaged.csproj" />
16+
<ProjectReference Include="$(SrcRoot)Worker/AzureManaged/Worker.AzureManaged.csproj" />
17+
<ProjectReference Include="$(SrcRoot)Analyzers/Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
18+
<ProjectReference Include="$(SrcRoot)Generators/Generators.csproj"
19+
OutputItemType="Analyzer"
20+
ReferenceOutputAssembly="false" />
21+
</ItemGroup>
22+
23+
</Project>
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
// This sample demonstrates activity versioning with [DurableTask(Version = "...")].
5+
// Versioned orchestrators and versioned activities can share the same logical
6+
// durable task names in one worker process. Plain activity calls inherit the
7+
// orchestration instance version by default, while version-qualified helpers
8+
// can explicitly override that routing when needed.
9+
10+
using Microsoft.DurableTask;
11+
using Microsoft.DurableTask.Client;
12+
using Microsoft.DurableTask.Client.AzureManaged;
13+
using Microsoft.DurableTask.Worker;
14+
using Microsoft.DurableTask.Worker.AzureManaged;
15+
using Microsoft.Extensions.Configuration;
16+
using Microsoft.Extensions.DependencyInjection;
17+
using Microsoft.Extensions.Hosting;
18+
19+
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
20+
21+
// Read the DTS connection string from configuration.
22+
string connectionString = builder.Configuration.GetValue<string>("DURABLE_TASK_SCHEDULER_CONNECTION_STRING")
23+
?? throw new InvalidOperationException(
24+
"Set DURABLE_TASK_SCHEDULER_CONNECTION_STRING. " +
25+
"For the local emulator: Endpoint=http://localhost:8080;TaskHub=default;Authentication=None");
26+
27+
// AddAllGeneratedTasks() registers every [DurableTask]-annotated class in this
28+
// project, including both versions of the orchestration and activity classes.
29+
builder.Services.AddDurableTaskWorker(wb =>
30+
{
31+
wb.AddTasks(tasks => tasks.AddAllGeneratedTasks());
32+
wb.UseDurableTaskScheduler(connectionString);
33+
});
34+
35+
// Configure the client. Unlike worker-level versioning, the client does not
36+
// stamp a single default version for every instance.
37+
builder.Services.AddDurableTaskClient(cb => cb.UseDurableTaskScheduler(connectionString));
38+
39+
IHost host = builder.Build();
40+
await host.StartAsync();
41+
42+
await using DurableTaskClient client = host.Services.GetRequiredService<DurableTaskClient>();
43+
44+
Console.WriteLine("=== Activity versioning ([DurableTask] Version) ===");
45+
Console.WriteLine();
46+
47+
Console.WriteLine("Scheduling CheckoutWorkflow v1 ...");
48+
string v1Id = await client.ScheduleNewCheckoutWorkflowV1InstanceAsync(5);
49+
OrchestrationMetadata v1 = await client.WaitForInstanceCompletionAsync(v1Id, getInputsAndOutputs: true);
50+
Console.WriteLine($" Result: {v1.ReadOutputAs<string>()}");
51+
Console.WriteLine();
52+
53+
Console.WriteLine("Scheduling CheckoutWorkflow v2 ...");
54+
string v2Id = await client.ScheduleNewCheckoutWorkflowV2InstanceAsync(5);
55+
OrchestrationMetadata v2 = await client.WaitForInstanceCompletionAsync(v2Id, getInputsAndOutputs: true);
56+
Console.WriteLine($" Result: {v2.ReadOutputAs<string>()}");
57+
Console.WriteLine();
58+
59+
Console.WriteLine("Scheduling CheckoutWorkflow v2 with explicit ShippingQuote v1 override ...");
60+
string overrideId = await client.ScheduleNewOrchestrationInstanceAsync(
61+
"ExplicitOverrideCheckoutWorkflow",
62+
input: 5,
63+
new StartOrchestrationOptions
64+
{
65+
Version = new TaskVersion("2"),
66+
});
67+
OrchestrationMetadata overrideResult = await client.WaitForInstanceCompletionAsync(overrideId, getInputsAndOutputs: true);
68+
Console.WriteLine($" Result: {overrideResult.ReadOutputAs<string>()}");
69+
Console.WriteLine();
70+
71+
Console.WriteLine("Done! Both versions ran in the same worker process.");
72+
Console.WriteLine("Default activity calls inherit the orchestration version, but versioned helpers can explicitly override it.");
73+
74+
await host.StopAsync();
75+
76+
/// <summary>
77+
/// CheckoutWorkflow v1 - default activity calls inherit orchestration version "1".
78+
/// </summary>
79+
[DurableTask("CheckoutWorkflow", Version = "1")]
80+
public sealed class CheckoutWorkflowV1 : TaskOrchestrator<int, string>
81+
{
82+
/// <inheritdoc />
83+
public override async Task<string> RunAsync(TaskOrchestrationContext context, int itemCount)
84+
{
85+
string quote = await context.CallActivityAsync<string>("ShippingQuote", itemCount);
86+
return $"Workflow v1 -> {quote}";
87+
}
88+
}
89+
90+
/// <summary>
91+
/// CheckoutWorkflow v2 - default activity calls inherit orchestration version "2".
92+
/// </summary>
93+
[DurableTask("CheckoutWorkflow", Version = "2")]
94+
public sealed class CheckoutWorkflowV2 : TaskOrchestrator<int, string>
95+
{
96+
/// <inheritdoc />
97+
public override async Task<string> RunAsync(TaskOrchestrationContext context, int itemCount)
98+
{
99+
string quote = await context.CallActivityAsync<string>("ShippingQuote", itemCount);
100+
return $"Workflow v2 -> {quote}";
101+
}
102+
}
103+
104+
/// <summary>
105+
/// CheckoutWorkflow v2 - explicitly overrides the inherited activity version.
106+
/// </summary>
107+
[DurableTask("ExplicitOverrideCheckoutWorkflow", Version = "2")]
108+
public sealed class ExplicitOverrideCheckoutWorkflowV2 : TaskOrchestrator<int, string>
109+
{
110+
/// <inheritdoc />
111+
public override async Task<string> RunAsync(TaskOrchestrationContext context, int itemCount)
112+
{
113+
string quote = await context.CallShippingQuoteV1Async(itemCount);
114+
return $"Workflow v2 explicit override -> {quote}";
115+
}
116+
}
117+
118+
/// <summary>
119+
/// ShippingQuote v1 - uses a flat shipping charge.
120+
/// </summary>
121+
[DurableTask("ShippingQuote", Version = "1")]
122+
public sealed class ShippingQuoteV1 : TaskActivity<int, string>
123+
{
124+
/// <inheritdoc />
125+
public override Task<string> RunAsync(TaskActivityContext context, int itemCount)
126+
{
127+
int total = (itemCount * 10) + 7;
128+
return Task.FromResult($"activity v1 quote: ${total} (flat $7 shipping)");
129+
}
130+
}
131+
132+
/// <summary>
133+
/// ShippingQuote v2 - applies a bulk discount and cheaper shipping.
134+
/// </summary>
135+
[DurableTask("ShippingQuote", Version = "2")]
136+
public sealed class ShippingQuoteV2 : TaskActivity<int, string>
137+
{
138+
/// <inheritdoc />
139+
public override Task<string> RunAsync(TaskActivityContext context, int itemCount)
140+
{
141+
int total = (itemCount * 10) + 5;
142+
if (itemCount >= 5)
143+
{
144+
total -= 10;
145+
}
146+
147+
return Task.FromResult($"activity v2 quote: ${total} ($10 bulk discount + $5 shipping)");
148+
}
149+
}

0 commit comments

Comments
 (0)