Files
agent-framework/dotnet/samples/DurableAgents/ConsoleApps/09_SubWorkflows/Program.cs
T
2026-01-30 18:42:52 -08:00

198 lines
9.2 KiB
C#

// Copyright (c) Microsoft. All rights reserved.
// This sample demonstrates how to use sub-workflows within a durable orchestration.
// Sub-workflows allow you to compose complex workflows from simpler, reusable components.
//
// The sample implements an order processing workflow with three sub-workflows:
// 1. PaymentProcessing - Validates and processes payment
// - Contains a nested FraudCheck sub-workflow (Level 2 nesting)
// 2. InventoryManagement - Checks and reserves inventory
// 3. ShippingArrangement - Arranges shipping and generates tracking
//
// The FraudCheck sub-workflow demonstrates Level 2 nesting (sub-workflow within sub-workflow):
// - AnalyzePatterns - Analyzes transaction patterns
// - CalculateRiskScore - Calculates fraud risk score
//
// Each sub-workflow runs as a separate orchestration instance, visible in the DTS dashboard.
// This provides:
// - Modular, reusable workflow components
// - Independent checkpointing and replay
// - Hierarchical visualization in the dashboard (including nested sub-workflows)
// - Failure isolation between parent and child workflows
using Microsoft.Agents.AI.DurableTask;
using Microsoft.Agents.AI.Workflows;
using Microsoft.DurableTask.Client.AzureManaged;
using Microsoft.DurableTask.Worker.AzureManaged;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using SubWorkflows;
// Get DTS connection string from environment variable
string dtsConnectionString = Environment.GetEnvironmentVariable("DURABLE_TASK_SCHEDULER_CONNECTION_STRING")
?? "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None";
// ============================================
// Step 1: Build the Fraud Check sub-sub-workflow (Level 2 nesting)
// ============================================
AnalyzePatterns analyzePatterns = new();
CalculateRiskScore calculateRiskScore = new();
Workflow fraudCheckWorkflow = new WorkflowBuilder(analyzePatterns)
.WithName("SubFraudCheck")
.WithDescription("Analyzes transaction patterns and calculates risk score")
.AddEdge(analyzePatterns, calculateRiskScore)
.Build();
// ============================================
// Step 2: Build the Payment Processing sub-workflow (now with nested sub-workflow)
// ============================================
ValidatePayment validatePayment = new();
ExecutorBinding fraudCheckExecutor = fraudCheckWorkflow.BindAsExecutor("FraudCheck");
ChargePayment chargePayment = new();
Workflow paymentWorkflow = new WorkflowBuilder(validatePayment)
.WithName("SubPaymentProcessing")
.WithDescription("Validates and processes payment for an order")
.AddEdge(validatePayment, fraudCheckExecutor)
.AddEdge(fraudCheckExecutor, chargePayment)
.Build();
// ============================================
// Step 3: Build the Inventory Management sub-workflow
// ============================================
CheckInventory checkInventory = new();
ReserveInventory reserveInventory = new();
Workflow inventoryWorkflow = new WorkflowBuilder(checkInventory)
.WithName("SubInventoryManagement")
.WithDescription("Checks availability and reserves inventory")
.AddEdge(checkInventory, reserveInventory)
.Build();
// ============================================
// Step 4: Build the Shipping Arrangement sub-workflow
// ============================================
SelectCarrier selectCarrier = new();
CreateShipment createShipment = new();
Workflow shippingWorkflow = new WorkflowBuilder(selectCarrier)
.WithName("SubShippingArrangement")
.WithDescription("Selects carrier and creates shipment")
.AddEdge(selectCarrier, createShipment)
.Build();
// ============================================
// Step 5: Build the Main Order Processing workflow using sub-workflows
// ============================================
// Bind sub-workflows as executors for use in the main workflow
ExecutorBinding paymentExecutor = paymentWorkflow.BindAsExecutor("Payment");
ExecutorBinding inventoryExecutor = inventoryWorkflow.BindAsExecutor("Inventory");
ExecutorBinding shippingExecutor = shippingWorkflow.BindAsExecutor("Shipping");
// Create entry and exit executors for the main workflow
OrderReceived orderReceived = new();
OrderCompleted orderCompleted = new();
// Build the main workflow: OrderReceived -> Payment -> Inventory -> Shipping -> OrderCompleted
Workflow orderProcessingWorkflow = new WorkflowBuilder(orderReceived)
.WithName("OrderProcessing")
.WithDescription("Processes an order through payment, inventory, and shipping")
.AddEdge(orderReceived, paymentExecutor)
.AddEdge(paymentExecutor, inventoryExecutor)
.AddEdge(inventoryExecutor, shippingExecutor)
.AddEdge(shippingExecutor, orderCompleted)
.Build();
// ============================================
// Step 6: Configure and start the host
// ============================================
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging => logging.SetMinimumLevel(LogLevel.Warning))
.ConfigureServices(services =>
{
// Register only the main workflow - sub-workflows are discovered automatically!
services.ConfigureDurableWorkflows(
options => options.Workflows.AddWorkflow(orderProcessingWorkflow),
workerBuilder: builder => builder.UseDurableTaskScheduler(dtsConnectionString),
clientBuilder: builder => builder.UseDurableTaskScheduler(dtsConnectionString));
})
.Build();
await host.StartAsync();
// Get the IWorkflowClient from DI
IWorkflowClient workflowClient = host.Services.GetRequiredService<IWorkflowClient>();
Console.WriteLine("╔══════════════════════════════════════════════════════════════════╗");
Console.WriteLine("║ Durable Sub-Workflows Sample ║");
Console.WriteLine("╠══════════════════════════════════════════════════════════════════╣");
Console.WriteLine("║ Main Workflow: OrderProcessing ║");
Console.WriteLine("║ ├── Payment (sub-workflow) ║");
Console.WriteLine("║ │ ├── ValidatePayment (1s) ║");
Console.WriteLine("║ │ ├── FraudCheck (sub-sub-workflow) ← Level 2 nesting! ║");
Console.WriteLine("║ │ │ ├── AnalyzePatterns (1s) ║");
Console.WriteLine("║ │ │ └── CalculateRiskScore (1s) ║");
Console.WriteLine("║ │ └── ChargePayment (2s) ║");
Console.WriteLine("║ ├── Inventory (sub-workflow) ║");
Console.WriteLine("║ │ ├── CheckInventory (1s) ║");
Console.WriteLine("║ │ └── ReserveInventory (2s) ║");
Console.WriteLine("║ └── Shipping (sub-workflow) ║");
Console.WriteLine("║ ├── SelectCarrier (1s) ║");
Console.WriteLine("║ └── CreateShipment (2s) ║");
Console.WriteLine("╚══════════════════════════════════════════════════════════════════╝");
Console.WriteLine();
Console.WriteLine("Open the DTS dashboard at http://localhost:8080 to see the");
Console.WriteLine("parent-child orchestration hierarchy in the Timeline view!");
Console.WriteLine();
Console.WriteLine("Enter an order ID (or 'exit'):");
while (true)
{
Console.Write("> ");
string? input = Console.ReadLine();
if (string.IsNullOrWhiteSpace(input) || input.Equals("exit", StringComparison.OrdinalIgnoreCase))
{
break;
}
try
{
await StartNewWorkflowAsync(input, orderProcessingWorkflow, workflowClient);
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
Console.WriteLine();
}
await host.StopAsync();
// Start a new workflow using IWorkflowClient
async Task StartNewWorkflowAsync(string orderId, Workflow workflow, IWorkflowClient client)
{
Console.WriteLine($"\nStarting order processing for '{orderId}'...");
await using DurableRun run = (DurableRun)await client.RunAsync(workflow, orderId);
Console.WriteLine($"Instance ID: {run.InstanceId}");
Console.WriteLine("Check the DTS dashboard Timeline tab to see sub-orchestrations!");
Console.WriteLine();
try
{
string? result = await run.WaitForCompletionAsync();
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine($"✓ Order completed: {result}");
Console.ResetColor();
}
catch (InvalidOperationException ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"✗ Failed: {ex.Message}");
Console.ResetColor();
}
}