Skip to content

Commit 3bdae51

Browse files
Merge pull request #224925 from jviau/durable-dotnet-isolated
Update durable docs for dotnet isolated
2 parents 2dce992 + 754dae9 commit 3bdae51

12 files changed

+638
-40
lines changed

articles/azure-functions/durable/TOC.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
href: ../functions-compare-logic-apps-ms-flow-webjobs.md?toc=/azure/azure-functions/durable/toc.json
1111
- name: Durable Functions versions
1212
href: durable-functions-versions.md
13+
- name: Durable Functions for .NET isolated
14+
href: durable-functions-dotnet-isolated-overview.md
1315
- name: Quickstarts
1416
expanded: true
1517
items:

articles/azure-functions/durable/durable-functions-bindings.md

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ The orchestration trigger binding supports both inputs and outputs. Here are som
5757

5858
The following example code shows what the simplest "Hello World" orchestrator function might look like. Note that this example orchestrator doesn't actually schedule any tasks.
5959

60-
# [C#](#tab/csharp)
60+
# [C# (InProc)](#tab/csharp-inproc)
6161

6262
```csharp
6363
[FunctionName("HelloWorld")]
@@ -71,6 +71,19 @@ public static string Run([OrchestrationTrigger] IDurableOrchestrationContext con
7171
> [!NOTE]
7272
> The previous code is for Durable Functions 2.x. For Durable Functions 1.x, you must use `DurableOrchestrationContext` instead of `IDurableOrchestrationContext`. For more information about the differences between versions, see the [Durable Functions Versions](durable-functions-versions.md) article.
7373
74+
# [C# (Isolated)](#tab/csharp-isolated)
75+
76+
```csharp
77+
[Function("HelloWorld")]
78+
public static string Run([OrchestrationTrigger] TaskOrchestrationContext context, string name)
79+
{
80+
return $"Hello {name}!";
81+
}
82+
```
83+
84+
> [!NOTE]
85+
> In both Durable functions in-proc and in .NET-isolated, the orchestration input can be extracted via `context.GetInput<T>()`. However, .NET-isolated also supports the input being supplied as a parameter, as shown above. The input binding will bind to the first parameter which has no binding attribute on it and is not a well-known type already covered by other input bindings (ie: `FunctionContext`).
86+
7487
# [JavaScript](#tab/javascript)
7588

7689
```javascript
@@ -119,7 +132,7 @@ public String helloWorldOrchestration(
119132

120133
Most orchestrator functions call activity functions, so here is a "Hello World" example that demonstrates how to call an activity function:
121134

122-
# [C#](#tab/csharp)
135+
# [C# (InProc)](#tab/csharp-inproc)
123136

124137
```csharp
125138
[FunctionName("HelloWorld")]
@@ -135,6 +148,18 @@ public static async Task<string> Run(
135148
> [!NOTE]
136149
> The previous code is for Durable Functions 2.x. For Durable Functions 1.x, you must use `DurableOrchestrationContext` instead of `IDurableOrchestrationContext`. For more information about the differences between versions, see the [Durable Functions versions](durable-functions-versions.md) article.
137150
151+
# [C# (Isolated)](#tab/csharp-isolated)
152+
153+
```csharp
154+
[Function("HelloWorld")]
155+
public static async Task<string> Run(
156+
[OrchestrationTrigger] TaskOrchestrationContext context, string name)
157+
{
158+
string result = await context.CallActivityAsync<string>("SayHello", name);
159+
return result;
160+
}
161+
```
162+
138163
# [JavaScript](#tab/javascript)
139164

140165
```javascript
@@ -228,7 +253,7 @@ The activity trigger binding supports both inputs and outputs, just like the orc
228253

229254
The following example code shows what a simple `SayHello` activity function might look like:
230255

231-
# [C#](#tab/csharp)
256+
# [C# (InProc)](#tab/csharp-inproc)
232257

233258
```csharp
234259
[FunctionName("SayHello")]
@@ -249,6 +274,18 @@ public static string SayHello([ActivityTrigger] string name)
249274
}
250275
```
251276

277+
# [C# (Isolated)](#tab/csharp-isolated)
278+
279+
In the .NET-isolated worker, only serializable types representing your input are supported for the `[ActivityTrigger]`.
280+
281+
```csharp
282+
[FunctionName("SayHello")]
283+
public static string SayHello([ActivityTrigger] string name)
284+
{
285+
return $"Hello {name}!";
286+
}
287+
```
288+
252289
# [JavaScript](#tab/javascript)
253290

254291
```javascript
@@ -355,7 +392,7 @@ In .NET functions, you typically bind to [IDurableClient](/dotnet/api/microsoft.
355392

356393
Here's an example queue-triggered function that starts a "HelloWorld" orchestration.
357394

358-
# [C#](#tab/csharp)
395+
# [C# (InProc)](#tab/csharp-inproc)
359396

360397
```csharp
361398
[FunctionName("QueueStart")]
@@ -371,6 +408,19 @@ public static Task Run(
371408
> [!NOTE]
372409
> The previous C# code is for Durable Functions 2.x. For Durable Functions 1.x, you must use `OrchestrationClient` attribute instead of the `DurableClient` attribute, and you must use the `DurableOrchestrationClient` parameter type instead of `IDurableOrchestrationClient`. For more information about the differences between versions, see the [Durable Functions Versions](durable-functions-versions.md) article.
373410
411+
# [C# (Isolated)](#tab/csharp-isolated)
412+
413+
```csharp
414+
[Function("QueueStart")]
415+
public static Task Run(
416+
[QueueTrigger("durable-function-trigger")] string input,
417+
[DurableClient] DurableTaskClient client)
418+
{
419+
// Orchestration input comes from the queue message content.
420+
return client.ScheduleNewOrchestrationInstanceAsync("HelloWorld", input);
421+
}
422+
```
423+
374424
# [JavaScript](#tab/javascript)
375425

376426
**function.json**
@@ -502,7 +552,7 @@ If you're using JavaScript, Python, or PowerShell, the entity trigger is defined
502552
```
503553

504554
> [!NOTE]
505-
> Entity triggers are not yet supported in Java.
555+
> Entity triggers are not yet supported in Java or in the .NET-isolated worker.
506556
507557
By default, the name of an entity is the name of the function.
508558

articles/azure-functions/durable/durable-functions-diagnostics.md

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ For more information about what log events are available, see the [Durable Task
178178

179179
It's important to keep the orchestrator replay behavior in mind when writing logs directly from an orchestrator function. For example, consider the following orchestrator function:
180180

181-
# [C#](#tab/csharp)
181+
# [C# (InProc)](#tab/csharp-inproc)
182182

183183
```csharp
184184
[FunctionName("FunctionChain")]
@@ -196,6 +196,25 @@ public static async Task Run(
196196
}
197197
```
198198

199+
# [C# (Isolated)](#tab/csharp-isolated)
200+
201+
```csharp
202+
[Function("FunctionChain")]
203+
public static async Task Run(
204+
[OrchestrationTrigger] TaskOrchestrationContext context,
205+
FunctionContext executionContext)
206+
{
207+
ILogger log = executionContext.GetLogger("FunctionChain");
208+
log.LogInformation("Calling F1.");
209+
await context.CallActivityAsync("F1");
210+
log.LogInformation("Calling F2.");
211+
await context.CallActivityAsync("F2");
212+
log.LogInformation("Calling F3");
213+
await context.CallActivityAsync("F3");
214+
log.LogInformation("Done!");
215+
}
216+
```
217+
199218
# [JavaScript](#tab/javascript)
200219

201220
```javascript
@@ -271,7 +290,7 @@ Done!
271290
272291
If you want to only write logs on non-replay executions, you can write a conditional expression to log only if the "is replaying" flag is `false`. Consider the example above, but this time with replay checks.
273292

274-
# [C#](#tab/csharp)
293+
# [C# (InProc)](#tab/csharp-inproc)
275294

276295
```csharp
277296
[FunctionName("FunctionChain")]
@@ -311,6 +330,28 @@ public static async Task Run(
311330
> [!NOTE]
312331
> The previous C# examples are for Durable Functions 2.x. For Durable Functions 1.x, you must use `DurableOrchestrationContext` instead of `IDurableOrchestrationContext`. For more information about the differences between versions, see the [Durable Functions versions](durable-functions-versions.md) article.
313332
333+
# [C# (Isolated)](#tab/csharp-isolated)
334+
335+
In Durable Functions for .NET-isolated, you can create an `ILogger` that automatically filters out log statements during replay. The main difference with Durable Functions in-proc is that you do not provide an existing `ILogger`. This logger is created via the `TaskOrchestrationContext.CreateReplaySafeLogger` overloads.
336+
337+
```csharp
338+
[Function("FunctionChain")]
339+
public static async Task Run([OrchestrationTrigger] TaskOrchestrationContext context)
340+
{
341+
ILogger log = context.CreateReplaySafeLogger("FunctionChain");
342+
log.LogInformation("Calling F1.");
343+
await context.CallActivityAsync("F1");
344+
log.LogInformation("Calling F2.");
345+
await context.CallActivityAsync("F2");
346+
log.LogInformation("Calling F3");
347+
await context.CallActivityAsync("F3");
348+
log.LogInformation("Done!");
349+
}
350+
```
351+
352+
> [!NOTE]
353+
> The ability to wrap an existing `ILogger` into a replay-safe logger has been removed in Durable Functions for .NET isolated worker.
354+
314355
# [JavaScript](#tab/javascript)
315356

316357
```javascript
@@ -383,7 +424,7 @@ Done!
383424

384425
Custom orchestration status lets you set a custom status value for your orchestrator function. This custom status is then visible to external clients via the [HTTP status query API](durable-functions-http-api.md#get-instance-status) or via language-specific API calls. The custom orchestration status enables richer monitoring for orchestrator functions. For example, the orchestrator function code can invoke the "set custom status" API to update the progress for a long-running operation. A client, such as a web page or other external system, could then periodically query the HTTP status query APIs for richer progress information. Sample code for setting a custom status value in an orchestrator function is provided below:
385426

386-
# [C#](#tab/csharp)
427+
# [C# (InProc)](#tab/csharp-inproc)
387428

388429
```csharp
389430
[FunctionName("SetStatusTest")]
@@ -402,6 +443,22 @@ public static async Task SetStatusTest([OrchestrationTrigger] IDurableOrchestrat
402443
> [!NOTE]
403444
> The previous C# example is for Durable Functions 2.x. For Durable Functions 1.x, you must use `DurableOrchestrationContext` instead of `IDurableOrchestrationContext`. For more information about the differences between versions, see the [Durable Functions versions](durable-functions-versions.md) article.
404445
446+
# [C# (Isolated)](#tab/csharp-isolated)
447+
448+
```csharp
449+
[Function("SetStatusTest")]
450+
public static async Task SetStatusTest([OrchestrationTrigger] TaskOrchestrationContext context)
451+
{
452+
// ...do work...
453+
454+
// update the status of the orchestration with some arbitrary data
455+
var customStatus = new { completionPercentage = 90.0, status = "Updating database records" };
456+
context.SetCustomStatus(customStatus);
457+
458+
// ...do more work...
459+
}
460+
```
461+
405462
# [JavaScript](#tab/javascript)
406463

407464
```javascript

articles/azure-functions/durable/durable-functions-dotnet-entities.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ We currently offer two APIs for defining entities:
2424
This article focuses primarily on the class-based syntax, as we expect it to be better suited for most applications. However, the [function-based syntax](#function-based-syntax) may be appropriate for applications that wish to define or manage their own abstractions for entity state and operations. Also, it may be appropriate for implementing libraries that require genericity not currently supported by the class-based syntax.
2525

2626
> [!NOTE]
27-
> The class-based syntax is just a layer on top of the function-based syntax, so both variants can be used interchangeably in the same application.
27+
> The class-based syntax is just a layer on top of the function-based syntax, so both variants can be used interchangeably in the same application.
28+
29+
> [!NOTE]
30+
> Entities are not currently supported in Durable Functions for the dotnet-isolated worker.
2831
2932
## Defining entity classes
3033

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
---
2+
title: Overview of Durable Functions in the .NET isolated worker - Azure
3+
description: Learn about Durable Functions in the Azure Functions .NET isolated worker process, which supports non-LTS versions of .NET and .NET Framework apps.
4+
author: jviau
5+
ms.topic: overview
6+
ms.date: 01/24/2023
7+
ms.author: azfuncdf
8+
ms.devlang: csharp
9+
#Customer intent: As a developer, I want to learn about Durable Functions for the Azure Functions .NET isolated worker process.
10+
---
11+
12+
# Overview of Durable Functions in the .NET isolated worker
13+
14+
This article is an overview of Durable Functions in the [.NET isolated worker](../dotnet-isolated-process-guide.md). The isolated worker allows your Durable Functions app to run on a .NET version different than that of the Azure Functions host.
15+
16+
## Why use Durable Functions in the .NET isolated worker?
17+
18+
Using this model lets you get all the great benefits that come with the Azure Functions .NET isolated worker process. For more information, see [here](../dotnet-isolated-process-guide.md#why-net-functions-isolated-worker-process). Additionally, this new SDK includes some new [features](#feature-improvements-over-in-process-durable-functions).
19+
20+
### Feature improvements over in-process Durable Functions
21+
22+
- Orchestration input can be injected directly: `MyOrchestration([OrchestrationTrigger] TaskOrchestrationContext context, T input)`
23+
- Support for strongly typed calls and class-based activities and orchestrations (NOTE: in preview. For more information, see [here](#source-generator-and-class-based-activities-and-orchestrations).)
24+
- Plus all the benefits of the Azure Functions .NET isolated worker.
25+
26+
### Feature parity with in-process Durable Functions
27+
28+
Not all features from in-process Durable Functions have been migrated to the isolated worker yet. Some known missing features that will be addressed at a later date are:
29+
30+
- Durable Entities
31+
- `CallHttpAsync`
32+
33+
### Source generator and class-based activities and orchestrations
34+
35+
**Requirement**: add `<PackageReference Include="Microsoft.DurableTask.Generators" Version="1.0.0-preview.1" />` to your project.
36+
37+
By adding the source generator package, you get access to two new features:
38+
39+
- **Class-based activities and orchestrations**, an alternative way to write Durable Functions. Instead of "function-based", you write strongly-typed classes, which inherit types from the Durable SDK.
40+
- **Strongly typed extension methods** for invoking sub orchestrations and activities. These extension methods can also be used from "function-based" activities and orchestrations.
41+
42+
#### Function-based example
43+
44+
```csharp
45+
public static class MyFunctions
46+
{
47+
[Function(nameof(MyActivity))]
48+
public static async Task<string> MyActivity([ActivityTrigger] string input)
49+
{
50+
// implementation
51+
}
52+
53+
[Function(nameof(MyOrchestration))]
54+
public static async Task<string> MyOrchestration([OrchestrationTrigger] TaskOrchestrationContext context, string input)
55+
{
56+
// implementation
57+
return await context.CallActivityAsync(nameof(MyActivity), input);
58+
}
59+
}
60+
```
61+
62+
#### Class-based example
63+
64+
```csharp
65+
[DurableTask(nameof(MyActivity))]
66+
public class MyActivity : TaskActivity<string, string>
67+
{
68+
private readonly ILogger logger;
69+
70+
public MyActivity(ILogger<SayHelloTyped> logger) // activites have access to DI.
71+
{
72+
this.logger = logger;
73+
}
74+
75+
public async override Task<string> RunAsync(TaskActivityContext context, string input)
76+
{
77+
// implementation
78+
}
79+
}
80+
81+
[DurableTask(nameof(MyOrchestration))]
82+
public class MyOrchestration : TaskOrchestrator<string, string>
83+
{
84+
public async override Task<string> RunAsync(TaskOrchestrationContext context, string input)
85+
{
86+
ILogger logger = context.CreateReplaySafeLogger<MyOrchestration>(); // orchestrations do NOT have access to DI.
87+
88+
// An extension method was generated for directly invoking "MyActivity".
89+
return await context.CallMyActivityAsync(input);
90+
}
91+
}
92+
```
93+
94+
## Migration guide
95+
96+
This guide assumes you're starting with a .NET Durable Functions 2.x project.
97+
98+
### Update your project
99+
100+
The first step is to update your project to [Azure Functions .NET isolated](../migrate-version-3-version-4.md). Then, update your Durable Functions NuGet package references.
101+
102+
Old:
103+
104+
```xml
105+
<ItemGroup>
106+
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.DurableTask" Version="2.9.0" />
107+
</ItemGroup>
108+
```
109+
110+
New:
111+
112+
```xml
113+
<ItemGroup>
114+
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" Version="1.0.0" />
115+
</ItemGroup>
116+
```
117+
118+
### Update your code
119+
120+
Durable Functions for .NET isolated worker is an entirely new package with different types and namespaces. There are required changes to your code as a result, but many of the APIs line up with no changes needed.
121+
122+
#### Host.json schema
123+
124+
The schema for Durable Functions .NET isolated worker and Durable Functions 2.x has remained the same, no changes should be needed.
125+
126+
#### Public interface changes
127+
128+
This table isn't an exhaustive list of changes.
129+
130+
| 2.x | Isolated |
131+
| ---- | ---- |
132+
| `IDurableOrchestrationClient` | `DurableTaskClient` |
133+
| `IDurableOrchestrationClient.StartNewAsync` | `DurableTaskClient.ScheduleNewOrchestrationInstanceAsync` |
134+
| `IDurableOrchestrationContext` | `TaskOrchestrationContext` |
135+
| `IDurableOrchestrationContext.GetInput<T>()` | `TaskOrchestrationContext.GetInput<T>()` or inject input as a parameter: `MyOrchestration([OrchestrationTrigger] TaskOrchestrationContext context, T input)` |
136+
| `DurableActivityContext` | No equivalent |
137+
| `DurableActivityContext.GetInput<T>()` | Inject input as a parameter `MyActivity([ActivityTrigger] T input)` |
138+
| `CallActivityWithRetryAsync` | `CallActivityAsync`, include `TaskOptions` parameter with retry details. |
139+
| `CallSubOrchestratorWithRetryAsync` | `CallSubOrchestratorAsync`, include `TaskOptions` parameter with retry details. |
140+
| `CallHttpAsync` | No equivalent. Instead, write an activity that invokes your desired HTTP API. |
141+
| `CreateReplaySafeLogger(ILogger)` | `CreateReplaySafeLogger<T>()` or `CreateReplaySafeLogger(string)` |
142+
143+
#### Behavioral changes
144+
145+
- Serialization default behavior has changed from `Newtonsoft.Json` to `System.Text.Json`. For more information, see [here](./durable-functions-serialization-and-persistence.md).

0 commit comments

Comments
 (0)