Skip to content

Commit a7ecd60

Browse files
torosentCopilot
andcommitted
fix: update Azure Functions sample to FanOutFanIn pattern
Updated TracingChain.java to use the same Fan-Out/Fan-In pattern as the DTS sample (TracingPattern.java): 1s timer → 5× GetWeather → CreateSummary. This ensures both samples are consistent and demonstrate the same tracing capabilities. Updated samples/README.md with Azure Functions section explaining that Durable Functions tracing exports to Application Insights, not Jaeger. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 8bc2fb1 commit a7ecd60

File tree

2 files changed

+61
-54
lines changed

2 files changed

+61
-54
lines changed

samples-azure-functions/src/main/java/com/functions/TracingChain.java

Lines changed: 36 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -8,92 +8,80 @@
88
import com.microsoft.azure.functions.annotation.FunctionName;
99
import com.microsoft.azure.functions.annotation.HttpTrigger;
1010
import com.microsoft.durabletask.DurableTaskClient;
11+
import com.microsoft.durabletask.Task;
1112
import com.microsoft.durabletask.TaskOrchestrationContext;
1213
import com.microsoft.durabletask.azurefunctions.DurableActivityTrigger;
1314
import com.microsoft.durabletask.azurefunctions.DurableClientContext;
1415
import com.microsoft.durabletask.azurefunctions.DurableClientInput;
1516
import com.microsoft.durabletask.azurefunctions.DurableOrchestrationTrigger;
1617

18+
import java.time.Duration;
19+
import java.util.Arrays;
20+
import java.util.List;
1721
import java.util.Optional;
22+
import java.util.stream.Collectors;
1823

1924
/**
2025
* Sample demonstrating distributed tracing with Durable Functions.
2126
* <p>
27+
* Uses a Fan-Out/Fan-In pattern with a timer, matching the DTS sample (TracingPattern.java).
2228
* Trace context is automatically propagated from the HTTP trigger through the
23-
* orchestration to each activity and sub-orchestration. When Application Insights
24-
* or an OpenTelemetry exporter is configured, you will see correlated traces
25-
* across the entire workflow.
29+
* orchestration to each activity. When Application Insights is configured,
30+
* you will see correlated traces across the entire workflow.
2631
*/
2732
public class TracingChain {
2833

29-
@FunctionName("StartTracingChain")
30-
public HttpResponseMessage startTracingChain(
34+
@FunctionName("StartFanOutFanIn")
35+
public HttpResponseMessage startFanOutFanIn(
3136
@HttpTrigger(name = "req", methods = {HttpMethod.GET, HttpMethod.POST},
3237
authLevel = AuthorizationLevel.ANONYMOUS)
3338
HttpRequestMessage<Optional<String>> request,
3439
@DurableClientInput(name = "durableContext") DurableClientContext durableContext,
3540
final ExecutionContext context) {
36-
context.getLogger().info("Starting TracingChain orchestration");
41+
context.getLogger().info("Starting FanOutFanIn orchestration");
3742

3843
DurableTaskClient client = durableContext.getClient();
39-
String instanceId = client.scheduleNewOrchestrationInstance("TracingChain");
44+
String instanceId = client.scheduleNewOrchestrationInstance("FanOutFanIn");
4045
context.getLogger().info("Created orchestration with instance ID = " + instanceId);
4146
return durableContext.createCheckStatusResponse(request, instanceId);
4247
}
4348

4449
/**
45-
* Orchestration that chains activities and a sub-orchestration.
46-
* Trace context flows from the client through each step.
50+
* Fan-Out/Fan-In orchestration: waits 1s, then runs 5 GetWeather activities
51+
* in parallel, and finally calls CreateSummary to aggregate the results.
4752
*/
48-
@FunctionName("TracingChain")
49-
public String tracingChain(
53+
@FunctionName("FanOutFanIn")
54+
public String fanOutFanIn(
5055
@DurableOrchestrationTrigger(name = "ctx") TaskOrchestrationContext ctx) {
51-
String input = ctx.getInput(String.class);
52-
if (input == null) {
53-
input = "Hello";
54-
}
5556

56-
// Each activity execution creates a child span under the orchestration span
57-
String step1 = ctx.callActivity("TracingReverse", input, String.class).await();
58-
String step2 = ctx.callActivity("TracingCapitalize", step1, String.class).await();
57+
// Wait 1 second (creates a timer span)
58+
ctx.createTimer(Duration.ofSeconds(1)).await();
5959

60-
// Sub-orchestration also propagates trace context
61-
String result = ctx.callSubOrchestrator("TracingChildOrch", step2, String.class).await();
60+
// Fan-out: schedule 5 GetWeather activities in parallel
61+
List<String> cities = Arrays.asList("Seattle", "Tokyo", "London", "Paris", "Sydney");
62+
List<Task<String>> tasks = cities.stream()
63+
.map(city -> ctx.callActivity("GetWeather", city, String.class))
64+
.collect(Collectors.toList());
65+
List<String> results = ctx.allOf(tasks).await();
6266

63-
return result;
67+
// Fan-in: aggregate results
68+
String combined = String.join(", ", results);
69+
return ctx.callActivity("CreateSummary", combined, String.class).await();
6470
}
6571

66-
/**
67-
* Sub-orchestration that receives propagated trace context.
68-
*/
69-
@FunctionName("TracingChildOrch")
70-
public String tracingChildOrch(
71-
@DurableOrchestrationTrigger(name = "ctx") TaskOrchestrationContext ctx) {
72-
String input = ctx.getInput(String.class);
73-
return ctx.callActivity("TracingAddSuffix", input, String.class).await();
74-
}
75-
76-
@FunctionName("TracingReverse")
77-
public String tracingReverse(
78-
@DurableActivityTrigger(name = "input") String input,
79-
final ExecutionContext context) {
80-
context.getLogger().info("TracingReverse: " + input);
81-
return new StringBuilder(input).reverse().toString();
82-
}
83-
84-
@FunctionName("TracingCapitalize")
85-
public String tracingCapitalize(
86-
@DurableActivityTrigger(name = "input") String input,
72+
@FunctionName("GetWeather")
73+
public String getWeather(
74+
@DurableActivityTrigger(name = "city") String city,
8775
final ExecutionContext context) {
88-
context.getLogger().info("TracingCapitalize: " + input);
89-
return input.toUpperCase();
76+
context.getLogger().info("[GetWeather] Getting weather for: " + city);
77+
return city + "=72F";
9078
}
9179

92-
@FunctionName("TracingAddSuffix")
93-
public String tracingAddSuffix(
80+
@FunctionName("CreateSummary")
81+
public String createSummary(
9482
@DurableActivityTrigger(name = "input") String input,
9583
final ExecutionContext context) {
96-
context.getLogger().info("TracingAddSuffix: " + input);
97-
return input + "-traced";
84+
context.getLogger().info("[CreateSummary] Creating summary for: " + input);
85+
return "Weather Report: " + input;
9886
}
9987
}

samples/README.md

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,16 @@ Shows the trace from `durabletask-java-tracing-sample` service with spans coveri
5757

5858
### Jaeger — Trace Detail
5959

60-
Full span hierarchy showing the fan-out/fan-in pattern with paired Client+Server spans (matching .NET SDK):
60+
Full span hierarchy showing the fan-out/fan-in pattern with proper span durations:
6161
- `create_orchestration:FanOutFanIn` (root, internal)
62-
- `orchestration:FanOutFanIn` (server — orchestration execution)
63-
- `orchestration:FanOutFanIn:timer` (internal — durable timer wait)
64-
- `activity:GetWeather` ×5 (client — scheduling) → `activity:GetWeather` ×5 (server — execution)
65-
- `activity:CreateSummary` (client) → `activity:CreateSummary` (server)
62+
- `orchestration:FanOutFanIn` (server — full lifecycle, ~1.2s)
63+
- `orchestration:FanOutFanIn:timer` (internal — creation-to-fired, ~965ms)
64+
- `activity:GetWeather` ×5 (client — scheduling-to-completion, ~184ms) + ×5 (server — execution, ~25ms)
65+
- `activity:CreateSummary` (client, ~8ms) + (server, ~0.7ms)
6666

67-
15 spans total, Depth 3 — aligned with the .NET SDK trace structure.
67+
15 spans total, Depth 2.
68+
69+
> **Note:** Java OTel doesn't support `SetSpanId()` like .NET, so child spans appear as siblings under `create_orchestration` rather than nested under the orchestration span. All spans have meaningful durations.
6870
6971
![Jaeger trace detail](images/jaeger-full-trace-detail.png)
7072

@@ -91,3 +93,20 @@ The `FanOutFanIn` orchestration completed successfully with all activities.
9193
```bash
9294
docker stop jaeger dts-emulator && docker rm jaeger dts-emulator
9395
```
96+
97+
## Azure Functions Sample
98+
99+
The `samples-azure-functions` module contains a matching **Fan-Out/Fan-In** sample (`TracingChain.java`) for use with Azure Durable Functions. It uses the same pattern (1s timer → 5× `GetWeather``CreateSummary`) but runs as an HTTP-triggered Azure Function.
100+
101+
### Running
102+
103+
```bash
104+
cd samples-azure-functions
105+
../gradlew azureFunctionsPackage -PskipSigning -x downloadProtoFiles
106+
cd build/azure-functions/<app-name>
107+
func start
108+
```
109+
110+
Then trigger with: `curl http://localhost:7071/api/StartFanOutFanIn`
111+
112+
Distributed tracing in Durable Functions exports to **Application Insights** (not Jaeger/OTLP). Configure your `APPLICATIONINSIGHTS_CONNECTION_STRING` in `local.settings.json` to see traces.

0 commit comments

Comments
 (0)