Beginner

Introduction to Serverless with Azure Functions

What serverless is, Azure Functions, hosting plans, triggers and bindings and your first function.

Course: Introduction to Serverless with Azure Functions Level: Beginner to Intermediate Technologies: Azure Functions v4 / C# / .NET 8 / Azure Portal / Azure CLI


About this guide: This document is a comprehensive and enriched introduction to the world of serverless computing and Azure Functions. It covers serverless fundamentals, Azure Functions concepts, event-driven patterns, Durable Functions and integration with Logic Apps. Ideal for developers getting started with serverless.


Table of Contents

  1. What is Serverless?
  2. Azure Functions – Overview
  3. Hosting Plans
  4. Triggers and Bindings
  5. Practical Use Cases
  6. Developing Your First Azure Function
  7. Event-Driven Architecture
  8. Durable Functions – Introduction
  9. Integration with Logic Apps
  10. Development Options and Tools
  11. Best Practices for Beginners
  12. Glossary

Module 1 – What is Serverless? {#module-1}

The Serverless Revolution: Why It Changes Everything

The term serverless is misleading: there are servers somewhere. But you don’t manage them. That’s precisely what changes everything. Imagine being able to deploy code directly to the internet, without ever configuring a server, without managing OS updates, without thinking about scalability — Azure handles it.

flowchart LR
    subgraph "Traditional Approach (You manage everything)"
        Dev1[Developer] -->|Code| Deploy1[Manual deployment]
        Deploy1 --> VM[Virtual Machine\nOS to patch\nWeb server to configure]
        VM --> Scale1[Manual scaling\nor configured auto-scale]
        Scale1 --> App1[Application]
    end
    
    subgraph "Serverless Approach (Azure manages the infra)"
        Dev2[Developer] -->|Code only| AF[Azure Functions]
        AF -->|Automatically| Scale2[Scale 0 → ∞\ninstantly]
        AF -->|Pay| Pay[Only for\nactual executions]
    end
    
    style AF fill:#68217A,color:#fff
    style Pay fill:#107C10,color:#fff

Comparison: Serverless vs. Traditional vs. Containers

AspectTraditional (VM)Containers (AKS)Serverless (Azure Functions)
InfrastructureYou manage everything (OS, runtime, patches)You manage containersAzure manages everything
ScalabilityManual or configured auto-scaleKubernetes HPA/KEDAAutomatic, scale to zero
PaymentInstance always runningPods always runningPer execution only
StartupApplication always activeContainer always activeEvent-driven (cold start possible)
Time-to-marketSlow (infra to configure)Medium (Docker + K8s)Fast (a few minutes)
Developer focusInfra + codeDocker + codeCode only

The 5 Core Characteristics of Serverless

flowchart TD
    Serverless[Serverless] --> C1[1. No Infrastructure\nManagement\nAzure handles everything]
    Serverless --> C2[2. Auto-Scaling\nFrom 0 to thousands\nof instances automatically]
    Serverless --> C3[3. Consumption Pricing\nPay only\nfor actual executions]
    Serverless --> C4[4. Event-Driven\nTriggered by events\nnot by active timers]
    Serverless --> C5[5. Stateless\nEach invocation is\ncompletely independent]
    
    style Serverless fill:#68217A,color:#fff
    style C3 fill:#107C10,color:#fff
CharacteristicDescriptionBenefit
No Infrastructure ManagementAzure manages VMs, OS, runtime, patchesFocus on business code
Auto-ScalingAutomatic scale from 0 to 200+ instancesNo manual configuration
Consumption Pricing1 million free executions/monthMinimal or zero cost for small projects
Event-DrivenTriggered by real eventsMaximum responsiveness
StatelessEach invocation is isolatedInfinite horizontal scalability

When to Use Serverless?

Azure Functions is excellent for:

flowchart TD
    A{Is my workload suited for serverless?}
    A -->|Unpredictable traffic| B[✅ Perfect for Functions\nConsumption Plan]
    A -->|Event-based processing| C[✅ Ideal for Functions\nEvent-Driven]
    A -->|Rapid prototyping| D[✅ Excellent for Functions\nDeployment in minutes]
    A -->|Internal automation| E[✅ Very good for Functions\nWebhooks, integrations]
    A -->|Complex web application| F[⚠️ Consider App Service\nor Container Apps]
    A -->|Constant/predictable traffic| G[⚠️ Consider Dedicated\nor Premium Plan]

Perfect use cases for Azure Functions:

  1. Data processing: transform CSV files upon upload to Azure Blob Storage
  2. System integration: connect two services without a native connector (Event Hub → Cosmos DB)
  3. Lightweight APIs: REST microservices with unpredictable load
  4. Webhooks: receive notifications from GitHub, Stripe, Slack, etc.
  5. Scheduled tasks: database cleanup every night
  6. IoT: process sensor data in real time
  7. Notifications: send emails/SMS upon events
  8. Internal automation: automated DevOps processes

Module 2 – Azure Functions: Overview {#module-2}

Azure Functions Architecture

classDiagram
    class FunctionApp {
        <<Deployment Unit>>
        +string Name
        +HostingPlan Plan
        +StorageAccount Storage
        +AppInsights Monitoring
        +AppSettings Configuration
        +IEnumerable~Function~ Functions
    }
    
    class Function {
        <<Execution Unit>>
        +string FunctionName
        +Trigger Trigger
        +IEnumerable~InputBinding~ InputBindings
        +IEnumerable~OutputBinding~ OutputBindings
        +Execute()
    }
    
    class Trigger {
        <<Triggers the function>>
        +HTTP
        +Timer
        +Queue
        +ServiceBus
        +EventGrid
        +BlobStorage
        +CosmosDB
    }
    
    class Binding {
        <<Declarative connection>>
        +Input
        +Output
    }
    
    FunctionApp "1" *-- "1..*" Function
    Function "1" *-- "1" Trigger
    Function "1" *-- "0..*" Binding

The Function App: Deployment Unit

A Function App is the container that groups one or more Azure Functions together. It is the deployment and configuration unit. All functions in a Function App share:

  • The same hosting plan
  • The same App Settings (environment variables)
  • The same Application Insights connection
  • The same identity (Managed Identity)
// local.settings.json - Local development configuration
{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
    "ASPNETCORE_ENVIRONMENT": "Development",
    "MyCustomSetting": "Hello from local settings!"
  }
}

Supported Languages

LanguageRuntimeRecommended ModelSupport
C#.NET 8/9Isolated Worker✅ Full
JavaScriptNode.js 18/20v4 Model✅ Full
TypeScriptNode.js 18/20v4 Model✅ Full
PythonPython 3.11/3.12v2 Model✅ Full
JavaJava 11/17/21-✅ Full
PowerShellPS Core 7.4-✅ Full
Go-Custom Handler🟡 Community
Rust-Custom Handler🟡 Community

Module 3 – Hosting Plans {#module-3}

Choosing the Right Hosting Plan

flowchart TD
    A{What is your load profile?} --> B{Very unpredictable\nor near-zero traffic?}
    B -->|Yes| C[🟢 Consumption Plan\nPay-per-use\nScaling from 0 to 200 instances]
    B -->|No| D{Need VNet,\nprivate endpoints or\nhigh performance?}
    D -->|No| E{Moderate\npredictable traffic?}
    D -->|Yes| F{Existing App Service\ninfrastructure?}
    E -->|Yes| G[🔵 Flex Consumption\nBest compromise\nConfigurable 0 cold start]
    F -->|Yes| H[🟣 Dedicated Plan\nShare existing infra]
    F -->|No| I[🔴 Premium Plan\nPerformance + VNet\nPre-warmed instances]
PlanAverage CostCold StartTimeoutVNetMemory
ConsumptionVery low (often $0)Yes (1-10s)10 min1.5 GB
Flex ConsumptionLowConfigurable60 min512 MB - 4 GB
Premium EP1~$150/monthNoUnlimited3.5 GB
Premium EP2~$300/monthNoUnlimited7 GB
Dedicated B1~$13/monthNoUnlimited1.75 GB
Dedicated P1v3~$140/monthNoUnlimited8 GB

Consumption Plan Cost Calculation

Practical example:
- Application with 2 million executions/month
- Average duration: 100ms per execution
- Allocated RAM: 128MB (0.125 GB)

1. Execution cost:
   - Free included: 1,000,000 executions
   - Billable: 1,000,000 × $0.0000002 = $0.20
   
2. Resource cost:
   - GB-sec consumed: 2,000,000 × 0.1s × 0.125GB = 25,000 GB-sec
   - Free included: 400,000 GB-sec
   - Billable: 0 GB-sec (below threshold!)
   
MONTHLY TOTAL: $0.20  ← Practically free!

Getting started tip: For your first Azure Functions projects, always start with the Consumption plan. You probably won’t pay anything at all during your learning phase, and you can migrate to Premium later if needed.


Module 4 – Triggers and Bindings {#module-4}

Understanding Triggers

A trigger is what causes your function to execute. Each Azure Function has exactly ONE trigger.

flowchart LR
    subgraph "Event Sources (Triggers)"
        T1[🌐 HTTP Request\nRESTful API, Webhooks]
        T2[⏰ Timer\nCron expression]
        T3[📨 Storage Queue\nAsynchronous messages]
        T4[🔔 Service Bus\nEnterprise messaging]
        T5[📡 Event Grid\nAzure events]
        T6[🔄 Cosmos DB\nChange Feed]
        T7[📁 Blob Storage\nFile uploads]
        T8[🌊 Event Hubs\nIoT Streaming]
    end
    
    T1 & T2 & T3 & T4 & T5 & T6 & T7 & T8 -->|Trigger| AF[Azure Function\nYour Code]
    
    style AF fill:#68217A,color:#fff

Complete Table of Common Triggers

TriggerTrigger EventExample Use Case
HTTPHTTP/HTTPS requestREST API, Stripe webhook, web form
TimerCron scheduleNightly report, DB cleanup, batch
Storage QueueMessage in Azure QueueAsync processing, decoupling
Service BusTopic/queue messageEnterprise messaging, pub/sub
Event GridAzure or custom eventReact to Azure resource changes
Cosmos DBChange FeedSynchronization, cache invalidation
Blob StorageBlob created/modifiedImage processing, CSV transformation
Event HubsIoT or streaming eventIoT processing, real-time analytics
SQLSQL Server changeCDC, data synchronization

Understanding Bindings

Bindings are declarative connections to other services. They radically simplify the code needed to read or write data.

flowchart LR
    subgraph "Without Bindings"
        A1[Function] --> B1["new BlobServiceClient()\n+ Authenticate\n+ GetContainer()\n+ Upload()"]
        B1 --> C1[Blob Storage]
    end
    
    subgraph "With Output Binding"
        A2[Function] --> B2["[BlobOutput] byte[] result\n→ Return the data\nthat's it!"]
        B2 --> C2[Blob Storage]
    end
    
    style B2 fill:#107C10,color:#fff
TypeDirectionDescriptionExample
Trigger BindingTriggers + provides dataQueueTrigger delivers the message
Input BindingReads additional dataBlobInput reads a file
Output BindingWrites data to a serviceQueueOutput sends a message

Code Examples: Essential Triggers and Bindings

using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Logging;
using System.Net;

namespace MyFirstFunctions;

// =============================================
// EXAMPLE 1: Simple HTTP Trigger
// =============================================
public class HelloWorldFunction
{
    private readonly ILogger<HelloWorldFunction> _logger;

    public HelloWorldFunction(ILogger<HelloWorldFunction> logger)
    {
        _logger = logger;
    }

    [Function("HelloWorld")]
    public async Task<HttpResponseData> Run(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] 
        HttpRequestData req)
    {
        _logger.LogInformation("HelloWorld function triggered.");

        // Read the 'name' query parameter
        string name = req.Query["name"] ?? "World";

        var response = req.CreateResponse(HttpStatusCode.OK);
        response.Headers.Add("Content-Type", "text/plain; charset=utf-8");
        await response.WriteStringAsync($"Hello, {name}! Welcome to Azure Functions!");
        return response;
    }
}

// =============================================
// EXAMPLE 2: Timer Trigger (scheduled)
// =============================================
public class NightlyCleanupFunction
{
    private readonly ILogger<NightlyCleanupFunction> _logger;

    public NightlyCleanupFunction(ILogger<NightlyCleanupFunction> logger)
    {
        _logger = logger;
    }

    // Runs every day at 2:00 AM UTC
    [Function("NightlyCleanup")]
    public void Run(
        [TimerTrigger("0 0 2 * * *")] TimerInfo timerInfo)
    {
        _logger.LogInformation("Nightly cleanup started at {Time}", DateTime.UtcNow);
        
        if (timerInfo.IsPastDue)
        {
            _logger.LogWarning("The timer is past due!");
        }

        // Your cleanup logic here
        _logger.LogInformation("Nightly cleanup completed.");
    }
}

// =============================================
// EXAMPLE 3: Queue Trigger (async processing)
// =============================================
public class ProcessEmailFunction
{
    private readonly ILogger<ProcessEmailFunction> _logger;

    public ProcessEmailFunction(ILogger<ProcessEmailFunction> logger)
    {
        _logger = logger;
    }

    [Function("ProcessEmail")]
    public async Task Run(
        [QueueTrigger("email-queue", Connection = "AzureWebJobsStorage")] 
        EmailMessage emailMessage)
    {
        _logger.LogInformation(
            "Processing email for {Recipient}", 
            emailMessage.To);

        // Simulate sending email
        await Task.Delay(100); // Call to email service
        
        _logger.LogInformation("Email sent to {Recipient}", emailMessage.To);
    }
}

public record EmailMessage(string To, string Subject, string Body);

Example: Blob Trigger + Output Binding

// Automatic CSV → JSON file transformation
public class CsvToJsonFunction
{
    private readonly ILogger<CsvToJsonFunction> _logger;

    public CsvToJsonFunction(ILogger<CsvToJsonFunction> logger)
    {
        _logger = logger;
    }

    [Function("CsvToJson")]
    [BlobOutput("json-output/{name}.json", Connection = "AzureWebJobsStorage")]
    public async Task<string> Run(
        [BlobTrigger("csv-uploads/{name}.csv", Connection = "AzureWebJobsStorage")] 
        string csvContent,
        string name)
    {
        _logger.LogInformation("Converting CSV file: {Name}.csv", name);

        // Convert CSV to JSON (simplified logic)
        var lines = csvContent.Split('\n', StringSplitOptions.RemoveEmptyEntries);
        var headers = lines[0].Split(',');
        
        var records = lines.Skip(1).Select(line =>
        {
            var values = line.Split(',');
            var record = new Dictionary<string, string>();
            for (int i = 0; i < headers.Length && i < values.Length; i++)
            {
                record[headers[i].Trim()] = values[i].Trim();
            }
            return record;
        }).ToList();

        var json = System.Text.Json.JsonSerializer.Serialize(records, new System.Text.Json.JsonSerializerOptions
        {
            WriteIndented = true
        });

        _logger.LogInformation("File {Name}.json created ({Count} records)", name, records.Count);
        return json; // Return JSON → will be written to the output Blob
    }
}

Module 5 – Practical Use Cases {#module-5}

Serverless E-Commerce Application Architecture

flowchart TD
    subgraph "Frontend"
        Web[React Web Application]
    end

    subgraph "Azure Functions - Backend"
        HTTP1[HTTP: GET /api/products\nList products]
        HTTP2[HTTP: POST /api/orders\nPlace an order]
        HTTP3["HTTP: GET /api/orders/{id}\nOrder status"]
        TIMER[Timer: Daily report\n2:00 AM every night]
        QUEUE[Queue: ProcessOrder\nAsync processing]
        BLOB[Blob: ProcessImage\nImage optimization]
        COSMOS_TRIGGER[Cosmos DB Trigger\nCache update]
    end

    subgraph "Storage"
        SQL["(Azure SQL\nProducts, Orders)"]
        STORAGE[Azure Blob Storage\nProduct images]
        QUEUE_SVC[Azure Storage Queue\nPending orders]
        COSMOS["(Cosmos DB\nHigh-speed catalog)"]
    end

    Web --> HTTP1 --> COSMOS
    Web --> HTTP2 --> QUEUE_SVC
    Web --> HTTP3 --> SQL
    QUEUE_SVC --> QUEUE --> SQL
    STORAGE --> BLOB
    TIMER --> SQL
    COSMOS --> COSMOS_TRIGGER

    style HTTP1 fill:#68217A,color:#fff
    style HTTP2 fill:#68217A,color:#fff

Complete Scenario: Image Processing

sequenceDiagram
    participant User as User
    participant Web as Website
    participant Blob as Azure Blob Storage
    participant AF as Azure Function\n(Blob Trigger)
    participant AI_Service as Azure Computer Vision

    User->>Web: Upload profile photo
    Web->>Blob: Store in "profile-uploads/"
    Blob->>AF: Triggers automatically
    AF->>AF: Resize image (400x400)
    AF->>AI_Service: Analyze content
    AI_Service-->>AF: Results (faces detected, safe content)
    AF->>Blob: Store in "profile-processed/"
    AF->>Blob: Store thumbnail in "profile-thumbs/"
    AF-->>User: Notification "Photo processed"

Module 6 – Developing Your First Azure Function {#module-6}

Prerequisites and Tools

Required tools:

ToolPurposeInstallation
.NET SDK 8.0+Compile C# codedotnet.microsoft.com
Azure Functions Core Tools v4Run locallynpm install -g azure-functions-core-tools@4
Visual Studio CodeRecommended IDEcode.visualstudio.com
Azure Functions Extension (VSCode)Templates and debuggingVS Code Marketplace
AzuriteAzure Storage emulatornpm install -g azurite

Create and Test Locally

# 1. Create a new Azure Functions project
func init MyFirstFunctionApp --worker-runtime dotnet-isolated

cd MyFirstFunctionApp

# 2. Create a first HTTP function
func new --name HelloWorld --template "HTTP trigger" --authlevel anonymous

# 3. Start the runtime locally
func start

# The output shows:
# Functions:
#   HelloWorld: [GET,POST] http://localhost:7071/api/HelloWorld

# 4. Test with curl
curl "http://localhost:7071/api/HelloWorld?name=Alice"
# Response: Hello, Alice. This HTTP triggered function executed successfully.

# 5. Test with PowerShell
Invoke-RestMethod "http://localhost:7071/api/HelloWorld?name=Bob"

Deploy to Azure

# Option 1: Via Azure CLI (simple script)
# Create resources
RESOURCE_GROUP="rg-myfunctions-dev"
LOCATION="eastus"
STORAGE="stmyfunctions$(openssl rand -hex 4)"
FUNCTION_APP="func-myfunctions-dev"

az group create --name $RESOURCE_GROUP --location $LOCATION
az storage account create --name $STORAGE --resource-group $RESOURCE_GROUP --sku Standard_LRS
az functionapp create \
  --name $FUNCTION_APP \
  --resource-group $RESOURCE_GROUP \
  --storage-account $STORAGE \
  --consumption-plan-location $LOCATION \
  --runtime dotnet-isolated \
  --runtime-version 8 \
  --functions-version 4

# Deploy the code
func azure functionapp publish $FUNCTION_APP

echo "✅ Deployed! URL: https://$FUNCTION_APP.azurewebsites.net/api/HelloWorld"

Module 7 – Event-Driven Architecture {#module-7}

Principles of Event-Driven Architecture

Events are discrete moments of action in your system. Azure Functions is designed to react to these moments in an isolated and independent way.

flowchart TD
    subgraph "Example: Doorbell Camera Event"
        Camera[📷 Doorbell Camera] -->|Detects someone| EventGrid[Azure Event Grid]
        EventGrid -->|Triggers| AF1[Function: SendSMSNotification]
        EventGrid -->|Triggers| AF2[Function: SaveSecurityLog]
        EventGrid -->|Triggers| AF3[Function: StartRecording]
        
        AF1 -->|SMS| Owner[📱 Owner]
        AF2 -->|Saves| DB["(Security Log DB)"]
        AF3 -->|Starts| Storage[💾 Video Storage]
    end
    
    style EventGrid fill:#0078D4,color:#fff
    style AF1 fill:#68217A,color:#fff
    style AF2 fill:#68217A,color:#fff
    style AF3 fill:#68217A,color:#fff

Pattern: Data Storage with Cosmos DB Trigger

// Scenario: Every change in Cosmos DB is archived in Blob Storage
public class CosmosDbArchiveFunction
{
    private readonly ILogger<CosmosDbArchiveFunction> _logger;

    public CosmosDbArchiveFunction(ILogger<CosmosDbArchiveFunction> logger)
    {
        _logger = logger;
    }

    [Function("ArchiveCosmosChanges")]
    [BlobOutput("cosmos-archive/{DateTime:yyyyMMdd}/{rand-guid}.json", 
        Connection = "AzureWebJobsStorage")]
    public string Run(
        [CosmosDBTrigger(
            databaseName: "MyDatabase",
            containerName: "Documents",
            Connection = "CosmosDBConnection",
            LeaseContainerName = "Leases",
            CreateLeaseContainerIfNotExists = true)]
        IReadOnlyList<JsonElement> changedDocuments)
    {
        _logger.LogInformation(
            "Archive: {Count} documents modified in Cosmos DB", 
            changedDocuments.Count);

        // Serialize and archive all changes
        var archiveData = new
        {
            Timestamp = DateTime.UtcNow,
            DocumentCount = changedDocuments.Count,
            Documents = changedDocuments
        };

        return System.Text.Json.JsonSerializer.Serialize(archiveData);
    }
}

Pattern: Webhook for External API

// Receive a GitHub webhook and process push events
public class GitHubWebhookFunction
{
    private readonly ILogger<GitHubWebhookFunction> _logger;

    public GitHubWebhookFunction(ILogger<GitHubWebhookFunction> logger)
    {
        _logger = logger;
    }

    [Function("GitHubWebhook")]
    public async Task<HttpResponseData> Run(
        [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "webhooks/github")] 
        HttpRequestData req)
    {
        // Verify GitHub signature
        var signature = req.Headers.TryGetValues("X-Hub-Signature-256", out var sigs)
            ? sigs.FirstOrDefault()
            : null;

        if (string.IsNullOrEmpty(signature))
        {
            _logger.LogWarning("Webhook received without signature - ignored");
            var badReq = req.CreateResponse(HttpStatusCode.BadRequest);
            return badReq;
        }

        var eventType = req.Headers.TryGetValues("X-GitHub-Event", out var events)
            ? events.FirstOrDefault()
            : "unknown";

        _logger.LogInformation("GitHub event received: {EventType}", eventType);

        switch (eventType)
        {
            case "push":
                var pushPayload = await req.ReadFromJsonAsync<GitHubPushEvent>();
                _logger.LogInformation(
                    "Push on {Repository}: {CommitCount} commits by {Author}",
                    pushPayload?.Repository?.FullName,
                    pushPayload?.Commits?.Count ?? 0,
                    pushPayload?.Pusher?.Name);
                break;

            case "pull_request":
                _logger.LogInformation("Pull request event received");
                break;
        }

        var response = req.CreateResponse(HttpStatusCode.OK);
        await response.WriteStringAsync("Webhook processed");
        return response;
    }
}

Pattern: Lightweight API with Timer

// Complete example: Timer that generates a report and stores it in Blob
public class WeeklyReportFunction
{
    private readonly ILogger<WeeklyReportFunction> _logger;

    public WeeklyReportFunction(ILogger<WeeklyReportFunction> logger)
    {
        _logger = logger;
    }

    // Every Monday at 9:00 AM UTC
    [Function("WeeklyReport")]
    [BlobOutput("reports/weekly-{DateTime:yyyyMMdd}.json", Connection = "AzureWebJobsStorage")]
    public string Run(
        [TimerTrigger("0 0 9 * * 1")] TimerInfo timerInfo)
    {
        _logger.LogInformation("Generating weekly report...");

        var report = new
        {
            ReportDate = DateTime.UtcNow,
            Period = "Week of " + DateTime.UtcNow.AddDays(-7).ToString("d"),
            Summary = "Automatically generated report",
            // ... real data
        };

        var json = System.Text.Json.JsonSerializer.Serialize(report, 
            new System.Text.Json.JsonSerializerOptions { WriteIndented = true });
        
        _logger.LogInformation("Report generated and stored in Blob Storage");
        return json;
    }
}

Module 8 – Durable Functions: Introduction {#module-8}

Why Durable Functions?

Standard Azure Functions are stateless. That’s perfect for 90% of cases. But when you need to:

  • Orchestrate multiple sequential steps
  • Execute activities in parallel
  • Wait for human approval
  • Create long-running workflows

Durable Functions is the solution.

flowchart TD
    subgraph "Standard Functions (Stateless)"
        SF1[Function A] -->|Each call| SF2[No memory between calls]
        SF2 -->|Cannot| SF3[Orchestrate long steps]
    end
    
    subgraph "Durable Functions (Stateful)"
        DF1[Starter Function] -->|Launches| ORCH[Orchestrator Function]
        ORCH -->|Step 1| ACT1[Activity: Check stock]
        ORCH -->|Step 2| ACT2[Activity: Process payment]
        ORCH -->|Step 3| ACT3[Activity: Send email]
        ORCH -->|State persisted| STORAGE[Azure Storage\nFull history]
    end
    
    style ORCH fill:#68217A,color:#fff
    style STORAGE fill:#0078D4,color:#fff

The 3 Types of Durable Functions

TypeRoleAllowed Code
Starter FunctionStarts the orchestrationEverything (HTTP, Queue, Timer, etc.)
Orchestrator FunctionCoordinates the workflowDETERMINISTIC code only!
Activity FunctionDoes the actual workEverything (I/O, HTTP, DB, etc.)

Simple Durable Functions Example

using Microsoft.Azure.Functions.Worker;
using Microsoft.DurableTask;
using Microsoft.DurableTask.Client;

namespace MyFirstDurable;

// =============================================
// ORCHESTRATOR
// =============================================
public class SimpleOrderOrchestration
{
    [Function(nameof(SimpleOrderOrchestration))]
    public async Task<string> RunOrchestrator(
        [OrchestrationTrigger] TaskOrchestrationContext context)
    {
        var orderId = context.GetInput<string>()!;
        var logger = context.CreateReplaySafeLogger(nameof(SimpleOrderOrchestration));
        
        logger.LogInformation("Starting workflow for order {OrderId}", orderId);

        // Step 1: Check stock (sequential)
        bool inStock = await context.CallActivityAsync<bool>(
            nameof(CheckInventory), orderId);
        
        if (!inStock)
            return $"Order {orderId} cancelled: out of stock";

        // Step 2: Process payment
        bool paymentOk = await context.CallActivityAsync<bool>(
            nameof(ProcessPayment), orderId);
        
        if (!paymentOk)
            return $"Order {orderId} cancelled: payment rejected";

        // Step 3: Send confirmation
        await context.CallActivityAsync(nameof(SendConfirmationEmail), orderId);

        return $"Order {orderId} successfully processed!";
    }

    // Activities
    [Function(nameof(CheckInventory))]
    public async Task<bool> CheckInventory([ActivityTrigger] string orderId, FunctionContext ctx)
    {
        // Check availability in DB
        return true; // Simulated
    }

    [Function(nameof(ProcessPayment))]
    public async Task<bool> ProcessPayment([ActivityTrigger] string orderId, FunctionContext ctx)
    {
        // Payment API call
        return true; // Simulated
    }

    [Function(nameof(SendConfirmationEmail))]
    public async Task SendConfirmationEmail([ActivityTrigger] string orderId, FunctionContext ctx)
    {
        // Send email via SendGrid
        Console.WriteLine($"Confirmation email sent for {orderId}");
    }

    // Starter Function (HTTP)
    [Function("StartOrderWorkflow")]
    public async Task<string> HttpStart(
        [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "orders/{orderId}/process")] 
        HttpRequestData req,
        [DurableClient] DurableTaskClient client,
        string orderId)
    {
        var instanceId = await client.ScheduleNewOrchestrationInstanceAsync(
            nameof(SimpleOrderOrchestration),
            orderId,
            new StartOrchestrationOptions { InstanceId = $"order-{orderId}" });

        return instanceId; // ID for tracking state
    }
}

The 5 Durable Functions Patterns

flowchart TD
    subgraph "Pattern 1: Chaining (Sequential)"
        C1[Activity A] --> C2[Activity B] --> C3[Activity C]
    end
    
    subgraph "Pattern 2: Fan-Out/Fan-In (Parallel)"
        FO[Orchestrator] --> FA1[Activity 1]
        FO --> FA2[Activity 2]
        FO --> FA3[Activity N]
        FA1 & FA2 & FA3 --> FI[Consolidate results]
    end
    
    subgraph "Pattern 3: Async HTTP"
        HTTP[HTTP Client] -->|POST /start| Start[Starter]
        Start -->|202 Accepted + Location| HTTP
        HTTP -->|GET /status| Status[Status Check]
        Status -->|200 + result| HTTP
    end
    
    subgraph "Pattern 4: Human Interaction"
        Auto[Automatic] -->|Waits| Human[Human approval]
        Human -->|Approves/Rejects| Continue[Continue workflow]
    end
    
    subgraph "Pattern 5: Monitor (Polling)"
        Mon[Monitor] -->|Checks| Check{Condition\nmet?}
        Check -->|No| Wait[Wait 5 min]
        Wait --> Mon
        Check -->|Yes| Done[Finish]
    end

Module 9 – Integration with Logic Apps {#module-9}

Logic Apps + Azure Functions = The Best of Both Worlds

Logic Apps is a low-code integration platform for orchestrating complex workflows. Azure Functions brings custom code. Together, they are very powerful.

flowchart LR
    subgraph "Logic Apps (Low-Code Orchestration)"
        LA[Logic App Designer\nDrag & drop workflow]
        LA -->|Email Connector| Email[Receive email]
        Email -->|Condition| Cond{Contains 'URGENT'?}
        Cond -->|Yes| AF[Call Azure Function\nExtract key data from email]
        Cond -->|No| Archive[Archive in SharePoint]
        AF --> Notify[Notify team\nvia Teams]
    end
    
    style AF fill:#68217A,color:#fff
    style LA fill:#0078D4,color:#fff

Use cases for integration:

ScenarioLogic AppsAzure Function
Visual orchestration✅ Graphical designerToo complex to code
Complex business logicToo verbose✅ Precise C# code
400+ service connectors✅ NativeMust be coded
High-frequency performanceLimited✅ Automatic scalability
Data transformationLimited✅ Free code

Module 10 – Development Options and Tools {#module-10}

Development Environment Comparison

OptionAdvantagesDisadvantagesRecommendation
VS Code + ExtensionCross-platform, free, templatesLess powerful than VS✅ Recommended for beginners
Visual Studio 2022Complete, advanced IntelliSenseWindows only✅ For C# experts
Azure PortalNo installationLimited, not for production⚠️ Testing only
CLI + Azure Cloud ShellAccessible everywhere, no installBasic interface🟡 Quick demos
GitHub CodespacesDev env in the cloudPaid🟡 Distributed teams

Azurite: Local Storage Emulator

# Install Azurite
npm install -g azurite

# Start Azurite (emulates Blob, Queue, Table Storage)
azurite --location ./azurite-data --debug ./azurite-debug.log

# In local.settings.json
{
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true"
  }
}

Module 11 – Best Practices for Beginners {#module-11}

The 10 Commandments of Azure Functions

#PracticeWhy
1Small and focused functionsFewer dependencies = faster startup
2No Thread.Sleep()Use Task.Delay() (async)
3Always use async/awaitNever block threads
4Dependency injection via Program.csTestable and maintainable code
5Logging with ILoggerStructured logs in Application Insights
6No secrets in codeUse App Settings + Key Vault
7Timeout appropriate to the planConsumption max 10 min
8Idempotence for queue functionsMessages can be replayed
9Application Insights configuredObservability in production
10Unit testsQuality and confidence

Common Beginner Mistakes

// ❌ MISTAKE 1: HttpClient in constructor with `new`
[Function("BadExample")]
public async Task<HttpResponseData> BadHttpClient(HttpRequestData req)
{
    using var client = new HttpClient(); // ❌ Creates a new client on each call!
    var data = await client.GetStringAsync("https://api.example.com/data");
    // ...
}

// ✅ CORRECT: Use IHttpClientFactory via dependency injection
public class GoodExample
{
    private readonly HttpClient _client;
    
    public GoodExample(IHttpClientFactory httpClientFactory)
    {
        _client = httpClientFactory.CreateClient("external-api"); // ✅ Reused!
    }
    
    [Function("GoodExample")]
    public async Task<HttpResponseData> Run(HttpRequestData req)
    {
        var data = await _client.GetStringAsync("data");
        // ...
    }
}

// ❌ MISTAKE 2: Returning void (not async)
[Function("BadVoid")]
public void BadVoidFunction([TimerTrigger("0 * * * * *")] TimerInfo timer)
{
    var result = DoSomethingAsync().Result; // ❌ Potential deadlock!
}

// ✅ CORRECT: Async Task
[Function("GoodAsync")]
public async Task GoodAsyncFunction([TimerTrigger("0 * * * * *")] TimerInfo timer)
{
    var result = await DoSomethingAsync(); // ✅ Non-blocking
}

Glossary {#glossary}

TermDefinition
BindingDeclarative connection to a service (Input = read, Output = write)
Cold StartDelay during the first startup of an inactive Function instance
Consumption PlanAzure Functions serverless plan, billed per execution
Cron ExpressionExpression format for Timer Trigger schedules (6 fields)
DefaultAzureCredentialAzure SDK class for automatic authentication
Durable FunctionsAzure Functions extension for stateful workflows
Event-DrivenReactive architecture triggered by events
Function AppDeployment unit containing one or more Azure Functions
ILoggerStandard .NET interface for logs (injected into Functions)
Isolated Worker ModelC# model: Functions in a separate .NET process from the runtime
MicroserviceIndependent service focused on a single responsibility
OrchestratorDurable function that coordinates the workflow
ServerlessInfrastructure managed by Azure, billed per usage
StatelessNo persistent state between invocations
TriggerEvent triggering the execution of an Azure Function
WebhookHTTP notification sent by a service upon an event

Appendix: Quick Reference

What is Serverless?

Serverless does not mean “no servers” — there are servers, but you don’t manage them.

Serverless anatomy:

AspectServerlessTraditional
InfrastructureManaged by AzureYou manage (VMs, OS, patches)
ScalabilityAutomatic (0 → ∞)Manual or configured auto-scale
PaymentPer execution (consumption)For the instance even if idle
StartupEvent-drivenAlways active

Key advantages of serverless:

  • No infrastructure management: focus on business code
  • Auto-scale: automatic handling of load spikes
  • Consumption pricing: pay only for executions
  • Event-driven: triggered by events

Azure Functions – Overview

Azure Functions is Microsoft’s serverless platform for running code without managing infrastructure.

Characteristics:

  • Minimal unit: one function (one responsibility)
  • Function App: logical container for grouping functions
  • Supports: C#, Python, JavaScript/TypeScript, Java, PowerShell, Go

Function App Structure:

MyFunctionApp (Azure Function App)
├── OrderProcessorFunction    ← Function 1
├── EmailNotificationFunction ← Function 2
├── ImageResizerFunction      ← Function 3
└── host.json                 ← Global configuration

Simple C# Example:

[FunctionName("HttpTriggerExample")]
public static async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequest req,
    ILogger log)
{
    log.LogInformation("HTTP trigger function processed a request.");
    
    string name = req.Query["name"];
    string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
    dynamic data = JsonConvert.DeserializeObject(requestBody);
    name = name ?? data?.name;
    
    return name != null
        ? (ActionResult)new OkObjectResult($"Hello, {name}")
        : new BadRequestObjectResult("Please pass a name");
}

Hosting Plans

5 hosting options:

PlanCold StartScaleVNetBest For
ConsumptionYes0→200 instancesNoTrue serverless, sporadic
Flex ConsumptionReducedConfigurable✅ YesVNet required + flexible scale
PremiumNo (pre-warmed)Configurable✅ YesPerformance, no cold start
Dedicated (App Service)NoManual/auto✅ YesConstant usage
Container AppsPer configKEDA-based✅ YesKubernetes microservices

Triggers and Bindings

Each function has exactly ONE trigger.

HTTP Trigger:

[FunctionName("GetOrders")]
public static async Task<IActionResult> GetOrders(
    [HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest req,
    ILogger log)
{
    // Levels: Anonymous, Function (key), Admin
    var orders = await orderService.GetAllOrders();
    return new OkObjectResult(orders);
}

Timer Trigger (NCrontab syntax):

[FunctionName("DailyReport")]
public static void Run(
    [TimerTrigger("0 0 8 * * 1-5")] TimerInfo timer,  // 8:00 AM Mon-Fri
    ILogger log)
{
    log.LogInformation($"Daily report triggered at: {DateTime.Now}");
}

NCrontab Syntax: {second} {minute} {hour} {day} {month} {dayOfWeek}

"0 0 * * * *"       → Every hour
"0 */5 * * * *"     → Every 5 minutes
"0 0 8 * * 1-5"     → 8:00 AM, Monday-Friday
"0 0 0 1 * *"       → Midnight on the 1st of each month

Durable Functions

3 types of Durable Functions:

TypeRole
Client FunctionStarts the orchestration, can query state
Orchestrator FunctionDefines the workflow (steps, conditions, fan-out)
Activity FunctionAtomic unit of work (single task)

Pattern 1: Function Chaining (sequential execution):

[FunctionName("OrderProcessingOrchestrator")]
public static async Task<string> RunOrchestrator(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    // Sequential execution
    var orderId = await context.CallActivityAsync<string>("ValidateOrder", null);
    var payment = await context.CallActivityAsync<bool>("ProcessPayment", orderId);
    var shipment = await context.CallActivityAsync<string>("CreateShipment", orderId);
    var notification = await context.CallActivityAsync("SendConfirmation", shipment);
    
    return $"Order {orderId} complete: {shipment}";
}

Pattern 2: Fan-out / Fan-in (parallel):

[FunctionName("ParallelProcessing")]
public static async Task<string[]> RunOrchestrator(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    var items = context.GetInput<List<string>>();
    
    // Fan-out: launch all activities in parallel
    var tasks = items.Select(item => 
        context.CallActivityAsync<string>("ProcessItem", item)).ToList();
    
    // Fan-in: wait for all to complete
    string[] results = await Task.WhenAll(tasks);
    
    return results;
}

Pattern 3: Human Interaction (human approval):

[FunctionName("ApprovalOrchestrator")]
public static async Task RunOrchestrator(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    // Send approval request email
    await context.CallActivityAsync("SendApprovalEmail", null);
    
    // Wait for external event (up to 7 days)
    bool approved = await context.WaitForExternalEvent<bool>("Approved", 
        TimeSpan.FromDays(7));
    
    if (approved)
        await context.CallActivityAsync("ProcessApproval", null);
    else
        await context.CallActivityAsync("EscalateToPendingApprovals", null);
}

Summary and Key Points

ConceptDescription
ServerlessManaged infrastructure, auto-scale, pay-per-execution
Function AppLogical container for grouping Azure Functions
TriggerStarts execution (1 per function)
BindingDeclarative connection to services (input/output)
Consumption PlanTrue serverless, cold start, scale 0→200
Premium PlanPre-warmed, no cold start, VNet
NCrontabCron syntax for Timer Trigger
Durable FunctionsAzure Functions with state for long-running workflows
OrchestratorDefines the workflow in Durable Functions
Activity FunctionAtomic unit of work in Durable Functions
Fan-out/Fan-inLaunch tasks in parallel, wait for all
Human InteractionWait for an external event (approval)

Search Terms

serverless · azure · functions · microsoft · durable · triggers · architecture · bindings · hosting · pattern · api · apps · comparison · development · event-driven · function · logic · plan · plans · storage · tools · trigger

Interested in this course?

Contact us to book it or get a custom training plan for your team.