Skip to main content

Azure Durable Functions

What is Durable Functions?

Durable Functions is an extension of Azure Functions that lets you write stateful workflows in a serverless environment.

The Problem It Solves

Regular Azure Functions are stateless - they run, finish, and forget everything. But real-world workflows often need to:

  • Wait for external events (human approval, API callbacks)
  • Chain multiple steps together reliably
  • Run long-running processes (hours/days)
  • Retry failed operations
  • Fan out work in parallel, then aggregate results

How It Works

The extension automatically manages:

  • ✅ State persistence (checkpointing)
  • ✅ Automatic restarts after failures
  • ✅ Replay of orchestration history
  • ✅ Durable timers (wait for days without paying for idle compute)

Function Types

TypeRole
OrchestratorDefines the workflow logic - calls other functions, waits, branches
ActivityThe actual work units - call APIs, process data, etc.
EntityStateful objects (like actors) for managing shared state
ClientStarts/queries/terminates orchestrations

Common Patterns

1. Function Chaining

Execute a sequence of functions in a specific order: A → B → C → D

2. Fan-out/Fan-in

Run N tasks in parallel, wait for all to complete, then aggregate results.

3. Async HTTP APIs

Long-running operations with status polling endpoints.

4. Human Interaction

Wait for external approval/input with configurable timeout.

5. Aggregator (Stateful Entities)

Collect and aggregate data over time into a single entity.

6. Monitor

Recurring process that monitors a condition until met.

Code Examples

Basic Orchestration (C#)

[Function(nameof(MyOrchestrator))]
public static async Task<List<string>> MyOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
var outputs = new List<string>();

// Chain activities - state survives failures
outputs.Add(await context.CallActivityAsync<string>("SayHello", "Tokyo"));
outputs.Add(await context.CallActivityAsync<string>("SayHello", "London"));
outputs.Add(await context.CallActivityAsync<string>("SayHello", "Seattle"));

return outputs;
}

[Function("SayHello")]
public static string SayHello([ActivityTrigger] string city)
{
return $"Hello, {city}!";
}

Fan-out/Fan-in Pattern

[Function(nameof(FanOutFanIn))]
public static async Task<int> FanOutFanIn(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
var tasks = new List<Task<int>>();

// Fan out - start all tasks in parallel
for (int i = 0; i < 10; i++)
{
tasks.Add(context.CallActivityAsync<int>("ProcessItem", i));
}

// Fan in - wait for all to complete
int[] results = await Task.WhenAll(tasks);

return results.Sum();
}

Human Approval with Timeout

[Function(nameof(ApprovalWorkflow))]
public static async Task<string> ApprovalWorkflow(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
await context.CallActivityAsync("SendApprovalRequest", null);

using var cts = new CancellationTokenSource();
var timeout = context.CreateTimer(
context.CurrentUtcDateTime.AddHours(72), cts.Token);
var approval = context.WaitForExternalEvent<bool>("ApprovalEvent");

var winner = await Task.WhenAny(approval, timeout);

if (winner == approval)
{
cts.Cancel();
return approval.Result ? "Approved" : "Rejected";
}

return "Timed out";
}

Durable Entity (Counter)

[Function(nameof(Counter))]
public static void Counter([EntityTrigger] TaskEntityDispatcher dispatcher)
{
dispatcher.DispatchAsync<CounterEntity>();
}

public class CounterEntity
{
public int Value { get; set; }

public void Add(int amount) => Value += amount;
public void Reset() => Value = 0;
public int Get() => Value;
}

When to Use

✅ Use Durable Functions for:

  • Multi-step workflows with dependencies
  • Long-running processes (hours/days/weeks)
  • Coordination between multiple services
  • Stateful event processing
  • Reliable retry logic
  • Human-in-the-loop workflows

❌ Don't use for:

  • Simple request/response APIs
  • Real-time, low-latency requirements (< 100ms)
  • When Logic Apps fits better (visual designer, 400+ connectors)
  • Simple stateless operations

Hosting Options

PlanBest For
ConsumptionSporadic workloads, pay-per-execution
Flex ConsumptionVariable load with fast scaling
PremiumPre-warmed instances, VNet, longer timeouts
DedicatedPredictable workloads, full control

Storage Backends

ProviderDescription
Azure StorageDefault. Uses queues, tables, and blobs
NetheriteHigh-throughput, uses Event Hubs + Azure Storage
MSSQLSQL Server/Azure SQL for state storage

Best Practices

  1. Keep orchestrators deterministic - No I/O, random, or DateTime.Now
  2. Use activity functions for I/O - API calls, database operations
  3. Idempotent activities - Activities may be retried
  4. Version your orchestrations - Use deployment slots for zero-downtime
  5. Monitor with Application Insights - Built-in telemetry support
  6. Set appropriate timeouts - Avoid infinite waits

Latest Updates (2024-2025)

  • Durable Task Scheduler - New dedicated SKU with predictable pricing
  • Extension v3.x - All new features, upgrade from v2.x recommended
  • .NET Isolated Worker - Full support with strongly-typed calls
  • OpenTelemetry - Unified observability support

Resources