Serverless Orchestration Showdown: Durable Functions vs. Logic Apps in Production

by G.R Badhon

The short story

Both are great at orchestrating distributed work. They differ in how you express control flow, how they bill you, and how they connect to the rest of Azure and SaaS. If your workflow looks like application logic with fan-out, compensation, timeouts, and tests, it likely belongs in Durable Functions. If it looks like glue across many services and SaaS with approvals, lookups, and data mapping, Logic Apps shines.

Head to head

Latency

  • Durable Functions: Typical 50 to 150 ms per activity on warm workers. Consumption plan can have cold starts. Premium greatly reduces cold starts.
  • Logic Apps: Usually 200 to 1000 ms per action. Can be higher when connectors throttle or apply retries.

Durability and flow control

  • Durable Functions: Event sourced orchestrator state in Azure Storage with exactly once orchestration semantics. Timers, external events, sub orchestrations, and deterministic replays are built in.
  • Logic Apps: Durable workflow engine with scopes and built in retry policies. Long running approvals and stateful waits are first class. Fix and resubmit from run history is straightforward.

Cost model

  • Durable Functions: You pay for requests, GB s compute, and Storage transactions. Best when steps are code and you keep them lean.
  • Logic Apps: Consumption charges per action. Standard is vCPU based. Best when connectors replace custom code.

Isolation and networking

  • Durable Functions: VNET integration on Premium. Use Private Endpoints for Storage, Key Vault, Event Hubs, and Data Lake. Control outbound with NAT and lock inbound with Application Gateway or Front Door private origins.
  • Logic Apps: Standard runs in your App Service plan with VNET integration. Built in connectors execute in tenant. Some managed connectors call public endpoints. Use Private Endpoints for first party services and IP allow lists or a data gateway for SaaS.

Error handling and idempotency

  • Durable Functions: Try catch with compensation and saga patterns. Design idempotent activities and add custom retry logic where needed.
  • Logic Apps: Per action retry policies and scopes. Dead letter patterns via scopes. Resubmit failed runs from the portal.

Observability and troubleshooting

  • Durable Functions: App Insights traces and metrics plus durable history tables. Correlate end to end with custom telemetry.
  • Logic Apps: Visual run history with per action inputs and outputs. App Insights integration for metrics and logs.

Throughput guidance

  • Durable Functions: 50 to 500 TPS per app with light I O bound activities. Scale out to thousands TPS when activities are short. Storage and queue throughput set practical limits.
  • Logic Apps: 1 to 50 TPS per workflow in Consumption depending on actions and connectors. Standard scales with plan size. External APIs often become the bottleneck.

Cost guidance

  • Durable Functions Consumption: Inexpensive for spiky loads. Cost driven by compute and storage operations.
  • Functions Premium or Dedicated: Fixed monthly per core. Predictable for VNET heavy or always on scenarios.
  • Logic Apps Consumption: Linear per action. Reduce steps with batching and built in mappings.
  • Logic Apps Standard: Fixed vCPU cost. Better for steady frequent runs and when you need VNET.

Worked example 1 – Order processing

Shape: validate request, reserve inventory, charge payment, create shipment, wait for approval up to 30 minutes, send notifications.

Why Durable Functions: multiple parallel activities, a time bound approval, and compensation if any step fails.

C# durable orchestrator

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.DurableTask;

public static class OrderOrchestrator
{
    [FunctionName("OrderOrchestrator")]
    public static async Task<OrderResult> Run(
        [OrchestrationTrigger] IDurableOrchestrationContext ctx)
    {
        var order = ctx.GetInput<Order>();

        // fan out - reserve inventory and charge payment in parallel
        var tasks = new List<Task>
        {
            ctx.CallActivityAsync("ReserveInventory", order),
            ctx.CallActivityAsync("ChargePayment", order)
        };
        await Task.WhenAll(tasks);

        // create shipment
        var shipment = await ctx.CallActivityAsync<Shipment>("CreateShipment", order);

        // wait for approval or timeout
        var deadline = ctx.CurrentUtcDateTime.AddMinutes(30);
        using var cts = new CancellationTokenSource();
        var approval = ctx.WaitForExternalEvent<string>("ManagerApproval");
        var timeout = ctx.CreateTimer(deadline, cts.Token);

        if (approval == await Task.WhenAny(approval, timeout))
        {
            cts.Cancel();
            await ctx.CallActivityAsync("NotifyCustomer", shipment);
            return new OrderResult { Id = order.Id, Status = "Approved" };
        }
        else
        {
            // compensation
            await ctx.CallActivityAsync("RefundPayment", order);
            await ctx.CallActivityAsync("ReleaseInventory", order);
            return new OrderResult { Id = order.Id, Status = "TimedOut" };
        }
    }
} 

Estimating

  • Steps per order: 6 to 8 activities plus one timer.
  • Throughput: 100 TPS steady state with bursts to 300.
  • Durable Functions Consumption will handle this if activities are I/O bound and short.
  • Functions Premium gives you zero cold starts and VNET, helpful for PCI scopes.

Networking

  • Place Storage, Key Vault, and downstream services behind Private Endpoints. Integrate the function app with a VNET and route egress via NAT with approved IPs.

Worked example 2 – Data ingestion with retries

Shape: ingest batches from a partner API, transform, store to Data Lake, retry on 429 or 5xx with exponential backoff, park poison batches for manual replay.

Why Logic Apps: rich connectors, built-in retries, scopes, and business friendly run history. Works well when the partner exposes an API with limits.

Logic Apps workflow JSON

{
  "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
  "contentVersion": "1.0.0.0",
  "triggers": {
    "timer": {
      "type": "Recurrence",
      "recurrence": { "frequency": "Minute", "interval": 5 }
    }
  },
  "actions": {
    "Fetch_Batch": {
      "type": "Http",
      "inputs": {
        "method": "GET",
        "uri": "https://partner.example.com/api/batch",
        "retryPolicy": { "type": "exponential", "count": 5, "interval": "PT10S", "minimumInterval": "PT5S", "maximumInterval": "PT2M" }
      },
      "runAfter": {}
    },
    "Transform": {
      "type": "InlineCode",
      "inputs": { "language": "JavaScript", "code": "// map fields here" },
      "runAfter": { "Fetch_Batch": [ "Succeeded" ] }
    },
    "Store": {
      "type": "ApiConnection",
      "inputs": { "host": { "connection": { "name": "azureblob_connection" } }, "method": "post", "path": "/datasets/default/files" },
      "runAfter": { "Transform": [ "Succeeded" ] }
    },
    "On_Failure": {
      "type": "Scope",
      "actions": {
        "Send_To_DLQ": { "type": "Http", "inputs": { "method": "POST", "uri": "https://mydlq.example.com" } }
      },
      "runAfter": { "Store": [ "Failed" ] }
    }
  },
  "outputs": {}
} 

Estimating

  • Steps per run: 3 to 6 depending on mapping and storage.
  • Throughput: 10 to 30 TPS typical due to upstream rate limits. Parallelize with concurrency control on the trigger.
  • Cost scales per action in Consumption. Standard plan is better when runs are frequent and constant.

Networking

  • Logic Apps Standard supports VNET integration. Use Private Endpoints for Storage and Key Vault. For SaaS connectors, use IP allow lists or private connectivity if the SaaS supports Private Link or private endpoints. On-premise Data Gateway if needed.

Decision tree

  1. Do you need many SaaS or first party connectors with minimal code and human approvals?
  2. Is end to end Private Endpoint only traffic and VNET isolation mandatory?
  3. Is your target throughput above 50 TPS with low latency and fan out patterns?
  4. Do you prefer code first with unit tests and custom telemetry?

Anti patterns to avoid

  • Long CPU bound work inside an orchestrator. Move heavy lifting to activities or downstream compute like Functions, Containers, or Data Factory.
  • Single huge Logic App with hundreds of actions. Split into smaller, reusable child workflows.
  • Chatty millisecond SLA workflows in Logic Apps. Use Durable Functions for lower latency.
  • Overusing orchestrators for simple linear flows. Keep it simple with a plain Function or a single Logic App.
  • Ignoring idempotency. Make every activity or action safe to retry.

Cost and throughput worksheet

When estimating, write down:

  • Actions per run, average execution time per step, payload size, expected TPS, burst profile.
  • For Durable Functions Consumption: monthly cost is roughly executions times price per million plus GB-s compute plus storage transactions. Keep activities short and I/O bound.
  • For Logic Apps Consumption: monthly cost is roughly total actions per month times price per action. Reduce steps by using built in mappings and batching.
  • For Standard or Premium plans: pick SKU to match your steady state CPU and memory. Model peak vs average.

Use this template to run the numbers: Download the decision matrix template.

Private Endpoints and isolation notes

  • Durable Functions: place Storage, Queues, Tables, Key Vault, Event Hubs, and Data Lake behind Private Endpoints. Use VNET integration on the function app with locked down outbound via NAT and restricted inbound via Application Gateway or Front Door private origins.
  • Logic Apps Standard: runs inside your App Service plan. Enable VNET integration. Built in connectors execute in your environment. For connectors that call out to PaaS services you own, use Private Endpoints. For third party SaaS, prefer private connectivity options when available or constrain outbound IPs and allow list them.

Recommendation

  • If your team builds product code and needs high throughput with testability and low latency, standardize on Durable Functions. Use Premium for VNET first environments.
  • If your team integrates many services and wants a visual run history with rich connectors and approvals, use Logic Apps. Use Standard when you need VNET and predictable cost.

You may also like