Advanced

Designing Serverless Architectures with Azure Functions

Event-driven design, hosting and scalability, resilience, security and serverless design patterns.

Level: Intermediate to Advanced
Technologies: Azure Functions v4 / .NET 8 / C# / Isolated Worker Model

About this guide: This document is a comprehensive reference for designing serverless architectures with Azure Functions. It covers architectural patterns, integration with Azure services, resilience, performance, and security best practices. Designed for cloud architects and developers.


Table of Contents

  1. Serverless and Event-Driven Architecture
  2. Hosting Plans and Scalability Strategies
  3. Integration with Azure Services
  4. Resilience, Performance, and High Availability
  5. Monitoring and Observability
  6. Security and Governance
  7. Advanced Design Patterns
  8. Complete Reference Architecture
  9. Best Practices and Pitfalls to Avoid
  10. Glossary

Module 1 – Serverless and Event-Driven Architecture {#module-1}

The Serverless Paradigm: Understanding Statelessness

In a serverless architecture, functions are designed to wake up, complete a single task, and shut down — without needing to remember what happened before or what comes next. This is the essence of statelessness. And it is precisely this stateless design that makes Azure Functions ideal for highly scalable event-driven systems.

Why statelessness is a strength, not a limitation:

The platform can instantiate thousands of instances across regions without coordination, because each invocation is completely isolated — it retains no context from a previous execution. Persistent state must instead be externalized to storage services (Azure Storage, Cosmos DB, Redis Cache) rather than maintained in memory.

flowchart LR
    subgraph "Stateless Function Execution"
        R1[Request 1] --> F1[Instance Function A]
        R2[Request 2] --> F2[Instance Function B]
        R3[Request 3] --> F3[Instance Function C]
        R4[Request 4] --> F4[Instance Function D]
        
        F1 & F2 & F3 & F4 --> DB["(External State\nAzure Storage\nCosmos DB)"]
    end
    
    style DB fill:#0078D4,color:#fff

Event-Driven Architecture: The Core Components

Event-driven architecture is at the heart of how Azure Functions operates. The system consists of three main elements:

flowchart TD
    P1[Producer 1\nUser action] -->|Emits an event| EC[Event Channel\nEvent Grid / Service Bus / Queue]
    P2[Producer 2\nSystem change] -->|Emits an event| EC
    P3[Producer 3\nExternal service] -->|Emits an event| EC
    
    EC -->|Distributes| C1[Consumer 1\nAzure Function]
    EC -->|Distributes| C2[Consumer 2\nAzure Function]
    EC -->|Distributes| C3[Consumer 3\nLogic App]
    
    style EC fill:#0078D4,color:#fff
    style C1 fill:#68217A,color:#fff
    style C2 fill:#68217A,color:#fff
ComponentRoleAzure Examples
ProducerGenerates and emits eventsWeb app, external API, IoT device, timer
Event ChannelTransports and routes eventsAzure Event Grid, Service Bus, Storage Queue, Event Hubs
ConsumerReacts to events in isolationAzure Functions, Logic Apps, webhooks

Pub/Sub Pattern (Publish-Subscribe)

In the Pub/Sub model, a single event can trigger multiple independent consumers. This is particularly useful when several services need to react differently to the same event.

Concrete example: New user registration

Azure Event Grid (Topic: "user-events")
    ↓ Event: { "type": "UserRegistered", "userId": "abc123", "email": "user@example.com" }
    ├── Function 1: Send welcome email (SendGrid)
    ├── Function 2: Log the analytics event (Application Insights)
    ├── Function 3: Create the initial user profile (Cosmos DB)
    └── Function 4: Notify the sales team (Slack webhook)
// Consumer 1: Sending the welcome email
using Azure.Messaging.EventGrid;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;

namespace ShopTicket.Functions;

public class WelcomeEmailFunction
{
    private readonly ILogger<WelcomeEmailFunction> _logger;
    private readonly IEmailService _emailService;

    public WelcomeEmailFunction(
        ILogger<WelcomeEmailFunction> logger,
        IEmailService emailService)
    {
        _logger = logger;
        _emailService = emailService;
    }

    [Function("SendWelcomeEmail")]
    public async Task Run(
        [EventGridTrigger] EventGridEvent eventGridEvent)
    {
        _logger.LogInformation("Event received: {EventType}", eventGridEvent.EventType);

        if (eventGridEvent.EventType != "UserRegistered")
            return;

        var userData = eventGridEvent.Data.ToObjectFromJson<UserRegisteredEvent>();
        
        await _emailService.SendWelcomeEmailAsync(
            userData.Email,
            userData.FirstName);
        
        _logger.LogInformation("Welcome email sent to {Email}", userData.Email);
    }
}

public record UserRegisteredEvent(string UserId, string Email, string FirstName);

Event Streaming Pattern

Event streaming is used for high-volume continuous data flows. Azure Event Hubs is the preferred solution for IoT, telemetry, and real-time analytics scenarios.

using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;

namespace ShopTicket.Functions;

public class TelemetryProcessorFunction
{
    private readonly ILogger<TelemetryProcessorFunction> _logger;
    private readonly IAnalyticsService _analyticsService;

    public TelemetryProcessorFunction(
        ILogger<TelemetryProcessorFunction> logger,
        IAnalyticsService analyticsService)
    {
        _logger = logger;
        _analyticsService = analyticsService;
    }

    /// <summary>
    /// Batch processing of Event Hubs events.
    /// A batch can contain up to 10 events (configurable).
    /// </summary>
    [Function("ProcessTelemetry")]
    public async Task Run(
        [EventHubTrigger(
            "telemetry-hub",
            Connection = "EventHubConnection",
            IsBatched = true)] 
        IReadOnlyList<SensorReading> sensorReadings,
        FunctionContext context)
    {
        _logger.LogInformation(
            "Processing a batch of {Count} sensor readings", 
            sensorReadings.Count);

        // Parallel batch processing
        var tasks = sensorReadings.Select(reading => 
            _analyticsService.ProcessReadingAsync(reading));
        
        await Task.WhenAll(tasks);

        // Aggregate statistics
        var avgTemperature = sensorReadings.Average(r => r.Temperature);
        var maxPressure = sensorReadings.Max(r => r.Pressure);
        
        _logger.LogInformation(
            "Batch processed. Average temperature: {AvgTemp:F2}°C, Max pressure: {MaxPressure:F2} hPa",
            avgTemperature, maxPressure);
    }
}

public record SensorReading(
    string SensorId,
    double Temperature,
    double Pressure,
    double Humidity,
    DateTime Timestamp);

Durable Functions – Stateful Workflows

While stateless functions offer speed and scalability, Durable Functions introduce stateful workflows that allow complex, multi-step operations to be coordinated reliably.

stateDiagram-v2
    [*] --> Pending: Order received
    Pending --> CheckingInventory: Start inventory check
    CheckingInventory --> InsufficientStock: Insufficient stock
    CheckingInventory --> ProcessingPayment: Stock available
    InsufficientStock --> [*]: Cancellation
    ProcessingPayment --> PaymentFailed: Payment failed
    ProcessingPayment --> SendingConfirmation: Payment succeeded
    PaymentFailed --> [*]: Refund if necessary
    SendingConfirmation --> Completed: Email sent
    Completed --> [*]: Success

The four main Durable Functions patterns:

PatternDescriptionUse Case
ChainingSequential steps, each depends on the previousE-commerce order processing
Fan-out/Fan-inParallel activities + wait for allBulk email sending, report generation
Async HTTP APIStart a long process + status pollingVideo transcription, image analysis
MonitorPeriodic polling until a condition is metMonitor availability of an external service
Human InteractionWait for human approval with timeoutSuspicious order validation
AggregatorAggregate events over a periodDaily sales metrics

Module 2 – Hosting Plans and Scalability Strategies {#module-2}

Detailed Comparison of Hosting Plans

The choice of hosting plan is fundamental. It determines how your application scales, its startup speed, cost, and available features.

flowchart TD
    A{Which plan to choose?} --> B{Predictable\nand critical workload?}
    B -->|Yes| C{Budget available?}
    B -->|No| D[Consumption Plan\n$$ Pay-per-use]
    C -->|Limited| E[Flex Consumption\n$$$ Optimal compromise]
    C -->|Available| F{Need VNet/\nPrivate Endpoints?}
    F -->|Yes| G[Premium Plan\n$$$$ Maximum performance]
    F -->|No| H{Existing\nApp Service infra?}
    H -->|Yes| I[Dedicated Plan\n$$$$ Shares infra]
    H -->|No| J{Containerized?}
    J -->|Yes| K[Container Apps\n$$$$$ Native Kubernetes]
    J -->|No| G
    
    style D fill:#107C10,color:#fff
    style E fill:#0078D4,color:#fff
    style G fill:#68217A,color:#fff
FeatureConsumptionFlex ConsumptionPremium EP1Premium EP2DedicatedContainer Apps
Billing modelPay-per-usePay-per-use + instancesInstances + scalingInstances + scalingMonthly planKubernetes
Cold StartsYes (2-5s)LowNo (pre-warmed)NoNoVariable
Default Timeout5 min30 min30 min30 minUnlimitedUnlimited
Max Timeout10 minUnlimitedUnlimitedUnlimitedUnlimitedUnlimited
VNet Integration
Private Endpoints
vCPU (instance)Shared1 vCPU1 vCPU2 vCPUVariableVariable
RAM (instance)1.5 GB2 GB3.5 GB7 GBVariableVariable
Max Scale Out200 instancesVariable100 instances100 instancesPer planUnlimited
Scale to Zero❌ (min 1)❌ (min 1)
Free quota1M exec/400K GB-secNoNoNoNoNo

Scaling Configuration

Consumption Plan: Automatic Behavior

// host.json - Scaling tuning for Consumption plan
{
  "version": "2.0",
  "extensions": {
    "queues": {
      "batchSize": 16,
      "newBatchThreshold": 8,
      "maxDequeueCount": 5,
      "maxPollingInterval": "00:00:02"
    },
    "serviceBus": {
      "maxConcurrentCalls": 16,
      "maxConcurrentSessions": 8,
      "prefetchCount": 50
    },
    "eventHubs": {
      "batchCheckpointFrequency": 5,
      "eventProcessorOptions": {
        "maxBatchSize": 100,
        "prefetchCount": 300
      }
    }
  },
  "functionTimeout": "00:10:00",
  "healthMonitor": {
    "enabled": true,
    "healthCheckInterval": "00:00:10",
    "healthCheckWindow": "00:02:00",
    "healthCheckThreshold": 6,
    "counterThreshold": 0.80
  }
}

Premium Plan: Dynamic Concurrency

// host.json - Dynamic concurrency (Premium Plan)
{
  "version": "2.0",
  "concurrency": {
    "dynamicConcurrencyEnabled": true,
    "snapshotPersistenceEnabled": true
  }
}

Essential Application Settings Configuration

# Complete configuration for a production Function App
az functionapp config appsettings set \
  --name func-shopticket-prod \
  --resource-group rg-shopticket-prod \
  --settings \
    "FUNCTIONS_EXTENSION_VERSION=~4" \
    "FUNCTIONS_WORKER_RUNTIME=dotnet-isolated" \
    "DOTNET_ISOLATED_JITCACHING=1" \
    "WEBSITE_RUN_FROM_PACKAGE=1" \
    "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING=@Microsoft.KeyVault(...)" \
    "APPLICATIONINSIGHTS_CONNECTION_STRING=@Microsoft.KeyVault(...)" \
    "ASPNETCORE_ENVIRONMENT=Production" \
    "WEBSITE_MAX_DYNAMIC_APPLICATION_SCALE_OUT=20"

Deployment Slots

Deployment slots enable zero-downtime updates with the ability to swap environments:

# Create a staging slot
az functionapp deployment slot create \
  --name func-shopticket-prod \
  --resource-group rg-shopticket-prod \
  --slot staging

# Deploy to the staging slot
az functionapp deployment source config-zip \
  --name func-shopticket-prod \
  --resource-group rg-shopticket-prod \
  --slot staging \
  --src ./functions-app.zip

# Test the staging slot
# URL: https://func-shopticket-prod-staging.azurewebsites.net

# Swap staging → production
az functionapp deployment slot swap \
  --name func-shopticket-prod \
  --resource-group rg-shopticket-prod \
  --slot staging \
  --target-slot production

Module 3 – Integration with Azure Services {#module-3}

Integration with Azure Event Grid

Azure Event Grid is Azure’s native event routing service. It uses a publish/subscribe model that delivers events from producers to consumers in near real-time.

sequenceDiagram
    participant BS as Azure Blob Storage
    participant EG as Azure Event Grid
    participant AF1 as Function: GenerateIndex
    participant AF2 as Function: NotifyUsers
    participant AF3 as Function: UpdateCDN

    BS->>EG: BlobCreated event\n{ blobUrl, contentType, size }
    EG->>AF1: Filtered by: subject contains "/products/"
    EG->>AF2: Filtered by: eventType = "Microsoft.Storage.BlobCreated"
    EG->>AF3: Filtered by: data.contentType = "image/*"
    
    AF1-->>EG: 200 OK (processed)
    AF2-->>EG: 200 OK (processed)
    AF3-->>EG: 200 OK (processed)

Key Event Grid concepts:

ConceptDescriptionExample
Event SourceService that emits eventsBlob Storage, Resource Group, Custom Topic
TopicEndpoint where events are sentSystem Topics (automatic) or Custom Topics
Event TypeCategory of the eventMicrosoft.Storage.BlobCreated, UserRegistered
SubscriptionFiltering and delivery ruleFilter by type, subject, or custom attributes
HandlerRecipient of the eventAzure Function, Logic App, Event Hub, Webhook
using Azure.Messaging.EventGrid;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;
using System.Text.Json;

namespace ShopTicket.Functions;

public class ProductCatalogEventHandler
{
    private readonly ILogger<ProductCatalogEventHandler> _logger;
    private readonly ISearchIndexService _searchService;
    private readonly ICdnService _cdnService;

    public ProductCatalogEventHandler(
        ILogger<ProductCatalogEventHandler> logger,
        ISearchIndexService searchService,
        ICdnService cdnService)
    {
        _logger = logger;
        _searchService = searchService;
        _cdnService = cdnService;
    }

    /// <summary>
    /// Reacts to product create/update events.
    /// Triggered by Azure Event Grid.
    /// </summary>
    [Function("HandleProductEvent")]
    public async Task Run(
        [EventGridTrigger] EventGridEvent eventGridEvent)
    {
        _logger.LogInformation(
            "Event received: {EventType} for {Subject}",
            eventGridEvent.EventType,
            eventGridEvent.Subject);

        switch (eventGridEvent.EventType)
        {
            case "ProductCreated":
            case "ProductUpdated":
                var product = eventGridEvent.Data.ToObjectFromJson<ProductData>();
                await HandleProductUpsertAsync(product, eventGridEvent.EventType);
                break;

            case "ProductDeleted":
                var deletedProduct = eventGridEvent.Data.ToObjectFromJson<ProductDeletedData>();
                await HandleProductDeletionAsync(deletedProduct.ProductId);
                break;

            case "Microsoft.Storage.BlobCreated":
                var blobData = eventGridEvent.Data.ToObjectFromJson<StorageBlobCreatedEventData>();
                if (blobData.Url.Contains("/product-images/"))
                {
                    await _cdnService.InvalidateCacheAsync(blobData.Url);
                }
                break;

            default:
                _logger.LogWarning("Unhandled event type: {EventType}", eventGridEvent.EventType);
                break;
        }
    }

    private async Task HandleProductUpsertAsync(ProductData product, string eventType)
    {
        _logger.LogInformation(
            "{EventType}: Product {ProductId} - {Name}", 
            eventType, product.ProductId, product.Name);
        
        await _searchService.UpsertProductAsync(product);
        _logger.LogInformation("Search index updated for product {ProductId}", product.ProductId);
    }

    private async Task HandleProductDeletionAsync(string productId)
    {
        _logger.LogInformation("Removing product {ProductId} from index", productId);
        await _searchService.DeleteProductAsync(productId);
    }
}

// Publish a custom event to Event Grid
public class ProductEventPublisher
{
    private readonly EventGridPublisherClient _publisherClient;

    public ProductEventPublisher(EventGridPublisherClient publisherClient)
    {
        _publisherClient = publisherClient;
    }

    public async Task PublishProductCreatedAsync(ProductData product)
    {
        var eventData = new EventGridEvent(
            subject: $"/products/{product.ProductId}",
            eventType: "ProductCreated",
            dataVersion: "1.0",
            data: new BinaryData(product));

        await _publisherClient.SendEventAsync(eventData);
    }
}

Integration with Azure Cosmos DB

Azure Cosmos DB integrates natively with Azure Functions via its Change Feed — a real-time stream that captures inserts and updates in Cosmos DB containers.

flowchart LR
    App[Application] -->|Insert/Update| Cosmos["(Cosmos DB\nContainer: Orders)"]
    Cosmos -->|Change Feed| AF[Azure Function\nCosmosDB Trigger]
    AF -->|Syncs| Cache["(Redis Cache)"]
    AF -->|Updates| Search[Azure Cognitive Search]
    AF -->|Sends notification| SB[Service Bus]
    
    style Cosmos fill:#0078D4,color:#fff
    style AF fill:#68217A,color:#fff
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;

namespace ShopTicket.Functions;

public class OrderChangeFeedProcessor
{
    private readonly ILogger<OrderChangeFeedProcessor> _logger;
    private readonly ICacheService _cacheService;
    private readonly INotificationService _notificationService;

    public OrderChangeFeedProcessor(
        ILogger<OrderChangeFeedProcessor> logger,
        ICacheService cacheService,
        INotificationService notificationService)
    {
        _logger = logger;
        _cacheService = cacheService;
        _notificationService = notificationService;
    }

    /// <summary>
    /// Triggered automatically by the Cosmos DB Change Feed.
    /// Processes changes in batches to optimize performance.
    /// </summary>
    [Function("ProcessOrderChanges")]
    public async Task Run(
        [CosmosDBTrigger(
            databaseName: "ShopTicketDB",
            containerName: "Orders",
            Connection = "CosmosDBConnection",
            LeaseContainerName = "Leases",
            CreateLeaseContainerIfNotExists = true,
            FeedPollDelay = 1000)] // Poll every 1 second
        IReadOnlyList<OrderDocument> changedOrders)
    {
        if (changedOrders is null || changedOrders.Count == 0)
            return;

        _logger.LogInformation(
            "Change Feed: {Count} order changes detected", 
            changedOrders.Count);

        // Parallel processing with concurrency limit
        var semaphore = new SemaphoreSlim(10); // Max 10 simultaneous operations
        var tasks = changedOrders.Select(async order =>
        {
            await semaphore.WaitAsync();
            try
            {
                // Update cache
                await _cacheService.SetAsync(
                    $"order:{order.OrderId}",
                    order,
                    TimeSpan.FromMinutes(30));

                // Notify if status changed to "Completed"
                if (order.Status == "Completed")
                {
                    await _notificationService.SendOrderCompletedNotificationAsync(order);
                }

                _logger.LogDebug(
                    "Order {OrderId} processed, status: {Status}", 
                    order.OrderId, order.Status);
            }
            finally
            {
                semaphore.Release();
            }
        });

        await Task.WhenAll(tasks);
        
        _logger.LogInformation(
            "Processing complete for {Count} changes", 
            changedOrders.Count);
    }
}

Important Cosmos DB considerations:

AspectRecommendationWhy
Request Units (RU)Plan the RU budget based on Change Feed volumeAvoid throttling (429 Too Many Requests)
Partition KeyChoose partition key carefullyMajor impact on cost and performance
Consistency LevelSession or Eventual for most casesStrong consistency = higher latency + cost
Lease ContainerOne lease container per applicationCoordination between Function instances
TTL (Time to Live)Enable on temporary documentsReduce storage costs

Integration with Azure Storage Queues

Azure Storage Queues provide simple, reliable messaging for decoupling application components. The philosophy is clear: instead of directly calling another service, a function drops a message onto the queue, and another processes it asynchronously.

sequenceDiagram
    participant HTTP as HTTP Function\n(Fast Response)
    participant Q as Storage Queue
    participant QF as Queue Function\n(Async Processing)
    participant DB as Database
    participant Email as Email Service

    HTTP->>Q: Drop OrderRequest message
    HTTP-->>Client: HTTP 202 Accepted (fast)
    
    Note over Q: Message stored durably
    
    Q->>QF: Trigger processing (≤ 30 sec visibility)
    QF->>DB: Save order
    QF->>Email: Send confirmation
    QF-->>Q: Delete message (success)
    
    alt Processing failure
        QF-->>Q: Message becomes visible again (retry)
        Note over Q,QF: Automatic retry up to maxDequeueCount (5)
        Q-->QF: Last retry failed
        Q->>QF: Move to poison queue
    end
using Azure.Storage.Queues.Models;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;

namespace ShopTicket.Functions;

public class OrderQueueProcessor
{
    private readonly ILogger<OrderQueueProcessor> _logger;
    private readonly IOrderService _orderService;

    public OrderQueueProcessor(
        ILogger<OrderQueueProcessor> logger,
        IOrderService orderService)
    {
        _logger = logger;
        _orderService = orderService;
    }

    // Main order processing
    [Function("ProcessOrderQueue")]
    public async Task ProcessOrder(
        [QueueTrigger("orders-queue", Connection = "AzureWebJobsStorage")] 
        OrderQueueMessage order)
    {
        _logger.LogInformation(
            "Processing order {OrderId}, attempt #{DequeueCount}", 
            order.OrderId, 
            order.DequeueCount);

        // Check idempotency
        if (await _orderService.IsAlreadyProcessedAsync(order.OrderId))
        {
            _logger.LogWarning("Order {OrderId} already processed, skipping", order.OrderId);
            return;
        }

        await _orderService.ProcessAsync(order);
    }

    // Handler for the poison queue (dead-letter)
    [Function("HandlePoisonOrder")]
    public async Task HandlePoison(
        [QueueTrigger("orders-queue-poison", Connection = "AzureWebJobsStorage")] 
        OrderQueueMessage poisonOrder)
    {
        _logger.LogError(
            "Order {OrderId} in poison queue after {Count} attempts. Manual intervention required.",
            poisonOrder.OrderId,
            poisonOrder.DequeueCount);

        // Alert the ops team
        await _orderService.FlagForManualReviewAsync(poisonOrder, "Poison queue - automatic processing failed");
    }
}

Integration with Azure Service Bus

Azure Service Bus provides advanced enterprise messaging features (sessions, native dead-letter queue, deferred messages, transactions).

using Azure.Messaging.ServiceBus;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;

namespace ShopTicket.Functions;

public class ServiceBusOrderProcessor
{
    private readonly ILogger<ServiceBusOrderProcessor> _logger;

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

    /// <summary>
    /// Processing Service Bus messages with sessions.
    /// Sessions guarantee ordered processing for the same OrderId.
    /// </summary>
    [Function("ProcessServiceBusOrder")]
    public async Task Run(
        [ServiceBusTrigger(
            "orders-topic/subscriptions/processing-service",
            Connection = "ServiceBusConnection",
            IsSessionsEnabled = true)] // Sessions for guaranteed processing order
        ServiceBusReceivedMessage message,
        ServiceBusMessageActions messageActions)
    {
        _logger.LogInformation(
            "Service Bus message received. MessageId: {MessageId}, SessionId: {SessionId}",
            message.MessageId,
            message.SessionId);

        try
        {
            var order = message.Body.ToObjectFromJson<OrderMessage>();
            
            // Process the order
            // ...
            
            // Confirm processing (Complete)
            await messageActions.CompleteMessageAsync(message);
            _logger.LogInformation("Message {MessageId} processed successfully", message.MessageId);
        }
        catch (BusinessException ex)
        {
            _logger.LogWarning(ex, "Business error for message {MessageId}", message.MessageId);
            // Dead-letter with explicit reason
            await messageActions.DeadLetterMessageAsync(message, 
                deadLetterReason: "BusinessRuleViolation",
                deadLetterErrorDescription: ex.Message);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Technical error for message {MessageId}", message.MessageId);
            // Abandon = message becomes visible again for retry
            await messageActions.AbandonMessageAsync(message);
            throw;
        }
    }
}

Module 4 – Resilience, Performance, and High Availability {#module-4}

Load Balancing and Fault Tolerance Patterns

Azure Functions handles load balancing automatically at the platform level. However, several parameters can be configured to optimize behavior under load.

Static vs. Dynamic Concurrency

flowchart TD
    subgraph "Static Concurrency (manually configured)"
        A1[Trigger: Service Bus] -->|maxConcurrentCalls: 16| B1[16 simultaneous processing\nper instance]
        B1 -->|High CPU?| C1[Performance issue\nrequires manual tuning]
    end
    
    subgraph "Dynamic Concurrency (Azure manages)"
        A2[Trigger: Service Bus] -->|dynamicConcurrencyEnabled: true| B2[Azure measures CPU/Memory]
        B2 -->|Low load| C2[Increases concurrency]
        B2 -->|High load| D2[Reduces concurrency]
        C2 & D2 --> E2[Automatic optimal balance]
    end
    
    style E2 fill:#107C10,color:#fff
// Retry pattern with Polly (for external calls in Activity Functions)
using Polly;
using Polly.Extensions.Http;

// Program.cs - Retry policy configuration
services.AddHttpClient("PaymentProvider")
    .AddPolicyHandler(GetRetryPolicy())
    .AddPolicyHandler(GetCircuitBreakerPolicy());

static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
        .WaitAndRetryAsync(
            retryCount: 3,
            sleepDurationProvider: retryAttempt => 
                TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)) // Exponential backoff
                + TimeSpan.FromMilliseconds(new Random().Next(0, 100))); // Jitter
}

static IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy()
{
    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .CircuitBreakerAsync(
            handledEventsAllowedBeforeBreaking: 5,
            durationOfBreak: TimeSpan.FromSeconds(30));
}

Multi-Region High Availability Patterns

flowchart LR
    subgraph "Primary Region (Active)"
        FA1[Function App\nEast US]
        Q1[Storage Queue\nEast US]
        DB1["(Cosmos DB\nEast US)"]
    end
    
    subgraph "Secondary Region (Passive)"
        FA2[Function App\nWest Europe]
        Q2[Storage Queue\nWest Europe]
        DB2["(Cosmos DB\nWest Europe\ngeo-replicated)"]
    end
    
    TM[Azure Traffic Manager\nor Azure Front Door] -.->|Failover if East US down| FA2
    
    Q1 --> FA1
    FA1 --> DB1
    DB1 <-->|Geo-replication| DB2
    
    style FA1 fill:#68217A,color:#fff
    style FA2 fill:#888,color:#fff
    style TM fill:#0078D4,color:#fff
# Azure Front Door configuration for active-active load balancing
az afd endpoint create \
  --resource-group rg-shopticket-prod \
  --profile-name afd-shopticket \
  --endpoint-name api-endpoint \
  --enabled-state Enabled

# Add origins (Function Apps in two regions)
az afd origin create \
  --resource-group rg-shopticket-prod \
  --profile-name afd-shopticket \
  --origin-group-name api-origins \
  --origin-name east-us-origin \
  --host-name func-shopticket-eastus.azurewebsites.net \
  --priority 1 \
  --weight 50

az afd origin create \
  --resource-group rg-shopticket-prod \
  --profile-name afd-shopticket \
  --origin-group-name api-origins \
  --origin-name west-europe-origin \
  --host-name func-shopticket-westeurope.azurewebsites.net \
  --priority 1 \
  --weight 50

Load Testing with Azure Load Testing

# Test configuration summary for ShopTicket API

Test Configuration:
  - Duration: 10 minutes
  - Concurrent users: 1000
  - Ramp-up: 2 minutes
  
Scenarios tested:
  1. GET /api/products - 500 req/sec
  2. POST /api/orders - 200 req/sec
  3. GET /api/orders/{id} - 300 req/sec
  
Metrics to monitor:
  - p95 response time < 500ms
  - Error rate < 1%
  - Throughput > 900 req/sec
  - Function App CPU < 80%
// Performance optimization: using distributed cache
using Microsoft.Extensions.Caching.Distributed;

public class ProductsFunction
{
    private readonly IDistributedCache _cache;
    private readonly IProductRepository _productRepository;
    private readonly ILogger<ProductsFunction> _logger;

    public ProductsFunction(
        IDistributedCache cache,
        IProductRepository productRepository,
        ILogger<ProductsFunction> logger)
    {
        _cache = cache;
        _productRepository = productRepository;
        _logger = logger;
    }

    [Function("GetProducts")]
    public async Task<HttpResponseData> GetProducts(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "products")] 
        HttpRequestData req)
    {
        const string cacheKey = "all-products";
        
        // Try to retrieve from Redis cache
        var cachedData = await _cache.GetStringAsync(cacheKey);
        
        if (cachedData is not null)
        {
            _logger.LogDebug("Cache hit for {CacheKey}", cacheKey);
            var cachedResponse = req.CreateResponse(System.Net.HttpStatusCode.OK);
            cachedResponse.Headers.Add("X-Cache", "HIT");
            await cachedResponse.WriteStringAsync(cachedData);
            return cachedResponse;
        }

        // Cache miss: load from database
        _logger.LogDebug("Cache miss for {CacheKey}, loading from DB", cacheKey);
        var products = await _productRepository.GetAllAsync();
        var json = System.Text.Json.JsonSerializer.Serialize(products);
        
        // Store in cache for 5 minutes
        await _cache.SetStringAsync(cacheKey, json, new DistributedCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5),
            SlidingExpiration = TimeSpan.FromMinutes(2)
        });

        var response = req.CreateResponse(System.Net.HttpStatusCode.OK);
        response.Headers.Add("X-Cache", "MISS");
        await response.WriteStringAsync(json);
        return response;
    }
}

Module 5 – Monitoring and Observability {#module-5}

Application Insights: Optimal Configuration

// Program.cs - Complete Application Insights configuration
var host = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults()
    .ConfigureServices(services =>
    {
        services.AddApplicationInsightsTelemetryWorkerService();
        services.ConfigureFunctionsApplicationInsights();
        
        // Configure sampling to reduce costs
        services.Configure<TelemetryConfiguration>(config =>
        {
            config.DefaultTelemetrySink.TelemetryProcessorChainBuilder
                .UseAdaptiveSampling(maxTelemetryItemsPerSecond: 5)
                .Build();
        });
    })
    .Build();
// host.json - Logging and sampling configuration
{
  "version": "2.0",
  "logging": {
    "logLevel": {
      "default": "Warning",
      "ShopTicket.Functions": "Information",
      "Host.Results": "Error",
      "Function": "Error"
    },
    "applicationInsights": {
      "samplingSettings": {
        "isEnabled": true,
        "maxTelemetryItemsPerSecond": 20,
        "excludedTypes": "Request;Exception"
      },
      "enableLiveTelemetry": true
    }
  }
}

Structured Logging – Best Practices

// ✅ Structured logging with rich context
_logger.LogInformation(
    "Order {OrderId} for customer {CustomerId} processed in {DurationMs}ms. Total: {Amount:C}",
    order.OrderId,
    order.CustomerId,
    stopwatch.ElapsedMilliseconds,
    order.TotalAmount);

// ✅ Scopes for log correlation
using (_logger.BeginScope(new Dictionary<string, object>
{
    ["OrderId"] = order.OrderId,
    ["CustomerId"] = order.CustomerId,
    ["CorrelationId"] = req.Headers.TryGetValues("X-Correlation-ID", out var vals) 
        ? vals.First() 
        : Guid.NewGuid().ToString()
}))
{
    // All logs in this block will have OrderId and CustomerId properties
    _logger.LogDebug("Inventory validation started");
    await CheckInventoryAsync(order);
    _logger.LogDebug("Inventory validation completed");
}

Distributed Tracing: Tracing a Complete Flow

Azure Functions automatically integrates distributed tracing via Application Insights. Each invocation creates an operation that can be correlated across multiple functions and services.

sequenceDiagram
    participant U as User
    participant AF1 as HTTP Function\n[operationId: abc-123]
    participant Q as Queue
    participant AF2 as Queue Function\n[operationId: abc-123]
    participant DB as Cosmos DB\n[dependencyId: xyz-456]

    U->>AF1: POST /api/orders
    AF1->>Q: SendMessage\n[parentId: req-789]
    AF1-->>U: 202 Accepted
    Q->>AF2: TriggerFunction\n[operationId: abc-123]
    AF2->>DB: Upsert document\n[dependencyId: xyz-456]
    DB-->>AF2: Success

KQL queries for investigation:

// Trace a complete order processing flow
let orderId = "12345-abcde";
union requests, dependencies, traces, exceptions
| where timestamp > ago(24h)
| where customDimensions["OrderId"] == orderId
   or customDimensions["prop__OrderId"] == orderId
| project timestamp, 
          itemType,
          name,
          duration,
          success,
          resultCode,
          message,
          operation_Id,
          operation_ParentId
| order by timestamp asc

// Global health dashboard
requests
| where timestamp > ago(1h)
| summarize 
    TotalCalls = count(),
    SuccessRate = round(countif(success == true) * 100.0 / count(), 1),
    AvgDuration = round(avg(duration)),
    P95Duration = round(percentile(duration, 95))
  by bin(timestamp, 5m), name
| render timechart

// Top 10 errors
exceptions
| where timestamp > ago(24h)
| summarize Count = count() by type, outerMessage
| top 10 by Count desc

Module 6 – Security and Governance {#module-6}

Securing HTTP Endpoints

flowchart LR
    Internet --> APIM[Azure API Management\n- Rate limiting\n- OAuth2 validation\n- IP filtering]
    APIM --> AF[Azure Functions\n- Function Keys\n- HTTPS only\n- Input validation]
    AF --> DB["(Databases\n- RBAC\n- Private Endpoint\n- TLS)"]
    
    KV[Azure Key Vault] -.->|Secrets| AF
    AAD[Microsoft Entra ID] -.->|OAuth2 tokens| APIM
    
    style APIM fill:#0078D4,color:#fff
    style AF fill:#68217A,color:#fff
    style KV fill:#FFB900,color:#000
// Validating Entra ID tokens in an HTTP Function
using Microsoft.Identity.Web;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;

namespace ShopTicket.Functions;

public class SecuredOrdersFunction
{
    private readonly ILogger<SecuredOrdersFunction> _logger;

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

    [Function("GetMyOrders")]
    public async Task<HttpResponseData> GetMyOrders(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "my/orders")] 
        HttpRequestData req)
    {
        // Validate the JWT Bearer token
        var authHeader = req.Headers.TryGetValues("Authorization", out var values)
            ? values.FirstOrDefault()
            : null;

        if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer "))
        {
            var unauthorized = req.CreateResponse(System.Net.HttpStatusCode.Unauthorized);
            await unauthorized.WriteStringAsync("Authentication token required");
            return unauthorized;
        }

        // Actual token validation is done via MSAL middleware
        // or via Azure API Management before reaching the Function
        var userId = req.FunctionContext.Items["UserId"] as string;
        
        if (userId is null)
        {
            var forbidden = req.CreateResponse(System.Net.HttpStatusCode.Forbidden);
            return forbidden;
        }

        // Business logic...
        var response = req.CreateResponse(System.Net.HttpStatusCode.OK);
        await response.WriteAsJsonAsync(new { userId, orders = new[] { "order1", "order2" } });
        return response;
    }
}

Managed Identities: Complete Configuration

# Managed Identity configuration for access to all services

FUNCTION_APP="func-shopticket-prod"
RG="rg-shopticket-prod"

# 1. Get the Principal ID
PRINCIPAL_ID=$(az functionapp identity show \
  --name $FUNCTION_APP \
  --resource-group $RG \
  --query principalId -o tsv)

# 2. Service Bus - read/write permissions
SB_RESOURCE_ID=$(az servicebus namespace show \
  --name sb-shopticket \
  --resource-group $RG \
  --query id -o tsv)

az role assignment create \
  --assignee $PRINCIPAL_ID \
  --role "Azure Service Bus Data Owner" \
  --scope $SB_RESOURCE_ID

# 3. Cosmos DB - data access
COSMOS_RESOURCE_ID=$(az cosmosdb show \
  --name cosmos-shopticket \
  --resource-group $RG \
  --query id -o tsv)

az cosmosdb sql role assignment create \
  --account-name cosmos-shopticket \
  --resource-group $RG \
  --role-definition-id "00000000-0000-0000-0000-000000000002" \
  --principal-id $PRINCIPAL_ID \
  --scope $COSMOS_RESOURCE_ID

# 4. Key Vault - secret access
az keyvault set-policy \
  --name kv-shopticket \
  --object-id $PRINCIPAL_ID \
  --secret-permissions get list

# 5. App Settings without secrets (identity-based connections)
az functionapp config appsettings set \
  --name $FUNCTION_APP \
  --resource-group $RG \
  --settings \
    "ServiceBusConnection__fullyQualifiedNamespace=sb-shopticket.servicebus.windows.net" \
    "CosmosDBConnection__accountEndpoint=https://cosmos-shopticket.documents.azure.com:443/" \
    "BlobStorageConnection__serviceUri=https://stshopticket.blob.core.windows.net"

OWASP Top 10: Protection in Azure Functions

OWASP RiskProtection MeasureAzure Functions Implementation
A01: Broken Access ControlRBAC, token validationManaged Identities + Entra ID OAuth2
A02: Cryptographic FailuresMandatory HTTPS, TLS 1.2+httpsOnly: true, minTlsVersion: '1.2'
A03: InjectionInput validation, parameterized queriesInput validation, EF Core parameterized queries
A05: Security MisconfigurationIaC, verified configBicep templates + Azure Policy
A06: Vulnerable ComponentsUp-to-date dependenciesDependabot, .NET security patches
A07: Authentication FailuresNo anonymous unless requiredAuthorizationLevel.Function minimum
A09: Security LoggingComplete audit logsApplication Insights + Log Analytics

Module 7 – Advanced Design Patterns {#module-7}

Saga Pattern: Distributed Transactions

/// <summary>
/// Saga pattern with Durable Functions.
/// Coordinates a distributed transaction across multiple services
/// with compensation on failure.
/// </summary>
[Function(nameof(OrderSagaOrchestration))]
public async Task RunSaga(
    [OrchestrationTrigger] TaskOrchestrationContext context)
{
    var order = context.GetInput<OrderRequest>()!;
    var logger = context.CreateReplaySafeLogger(nameof(OrderSagaOrchestration));
    
    var compensations = new Stack<Func<Task>>();

    try
    {
        // Step 1: Reserve inventory
        await context.CallActivityAsync(nameof(ReserveInventory), order);
        compensations.Push(async () => await context.CallActivityAsync(nameof(ReleaseInventory), order));
        logger.LogInformation("Inventory reserved for order {OrderId}", order.OrderId);

        // Step 2: Charge payment
        var paymentResult = await context.CallActivityAsync<PaymentResult>(nameof(ProcessPayment), order);
        compensations.Push(async () => await context.CallActivityAsync(nameof(RefundPayment), paymentResult));
        logger.LogInformation("Payment processed for order {OrderId}", order.OrderId);

        // Step 3: Generate tickets
        await context.CallActivityAsync(nameof(GenerateTickets), order);
        logger.LogInformation("Tickets generated for order {OrderId}", order.OrderId);

        // Step 4: Send confirmation
        await context.CallActivityAsync(nameof(SendConfirmation), order);
        logger.LogInformation("Saga completed successfully for order {OrderId}", order.OrderId);
    }
    catch (Exception ex)
    {
        logger.LogError(ex, "Saga failed for order {OrderId}, starting compensation", order.OrderId);
        
        // Execute compensations in reverse order
        while (compensations.Count > 0)
        {
            var compensate = compensations.Pop();
            try
            {
                await compensate();
            }
            catch (Exception compensationEx)
            {
                logger.LogCritical(compensationEx, 
                    "CRITICAL ERROR: Compensation failed for order {OrderId}", order.OrderId);
                // Alert the ops team immediately
            }
        }
        
        throw;
    }
}

CQRS Pattern with Azure Functions

classDiagram
    class CommandBus {
        +SendAsync(ICommand command)
    }
    class QueryBus {
        +QueryAsync(IQuery query)
    }
    class WriteModel {
        <<Azure Functions - Command Side>>
        +CreateOrderFunction
        +UpdateOrderFunction
        +CancelOrderFunction
    }
    class ReadModel {
        <<Azure Functions - Query Side>>
        +GetOrderFunction
        +ListOrdersFunction
        +GetOrderStatisticsFunction
    }
    class WriteDatabase {
        <<Azure SQL - Normalized>>
        +Orders
        +OrderItems
        +Customers
    }
    class ReadDatabase {
        <<Cosmos DB - Denormalized>>
        +OrderSummaries
        +CustomerOrders
    }
    class EventBus {
        <<Azure Service Bus>>
        +OrderCreated
        +OrderUpdated
    }

    CommandBus --> WriteModel
    QueryBus --> ReadModel
    WriteModel --> WriteDatabase
    WriteModel --> EventBus
    EventBus --> ReadModel
    ReadModel --> ReadDatabase

Module 8 – Complete Reference Architecture {#module-8}

flowchart TB
    subgraph "Public Zone"
        Web[SPA React/Blazor\nAzure Static Web Apps]
        Mobile[Mobile Application]
        Partner[Partner API]
    end

    subgraph "Secure Zone - API Gateway"
        APIM[Azure API Management\nAuth, Rate Limit, Routing]
        AFD[Azure Front Door\nGlobal Load Balancing CDN]
    end

    subgraph "Azure Functions - East Cluster"
        direction TB
        FA_HTTP[func-api-eastus\nHTTP Triggers]
        FA_PROC[func-processing-eastus\nQueue/Service Bus Triggers]
        FA_ORCH[func-orchestration-eastus\nDurable Functions]
        FA_SCHED[func-scheduler-eastus\nTimer Triggers]
    end

    subgraph "Messaging"
        SB[Azure Service Bus\nTopics & Queues]
        EG[Azure Event Grid\nEvent Routing]
        EH[Azure Event Hubs\nIoT/Telemetry Streaming]
    end

    subgraph "Persistence"
        SQL["(Azure SQL\nTransactional OLTP)"]
        COSMOS["(Cosmos DB\nGeo-replicated)"]
        BLOB[Azure Blob Storage\nFiles & Media]
        REDIS[Azure Redis Cache\nDistributed Cache]
    end

    subgraph "Security & Monitoring"
        KV[Key Vault\nSecrets]
        AAD[Microsoft Entra ID\nIdentities]
        AI[Application Insights\nTelemetry]
        LA[Log Analytics\nCentralized Logs]
        AM[Azure Monitor\nAlerts & Dashboards]
    end

    Web & Mobile --> AFD
    Partner --> APIM
    AFD --> APIM
    APIM --> FA_HTTP
    FA_HTTP --> SB
    FA_HTTP --> SQL
    FA_HTTP --> REDIS
    SB --> FA_PROC
    SB --> FA_ORCH
    FA_PROC --> COSMOS
    FA_PROC --> BLOB
    FA_SCHED --> SQL
    EH --> FA_PROC
    EG --> FA_HTTP

    KV -.->|secrets| FA_HTTP & FA_PROC & FA_ORCH
    AAD -.->|identity| FA_HTTP & APIM
    FA_HTTP & FA_PROC & FA_ORCH & FA_SCHED --> AI
    AI --> LA
    LA --> AM

    style APIM fill:#0078D4,color:#fff
    style FA_HTTP fill:#68217A,color:#fff
    style FA_PROC fill:#68217A,color:#fff
    style FA_ORCH fill:#68217A,color:#fff
    style FA_SCHED fill:#68217A,color:#fff

Module 9 – Best Practices and Pitfalls to Avoid {#module-9}

Architectural Review Checklist

flowchart TD
    A[New Azure Function] --> B{HTTP Trigger?}
    B -->|Yes| C[✅ AuthorizationLevel.Function minimum\n✅ Validate inputs\n✅ HTTPS only\n✅ CORS configured]
    B -->|No| D{Queue/Service Bus?}
    D -->|Yes| E[✅ Idempotency implemented\n✅ Dead-letter queue monitored\n✅ Retry policies configured\n✅ Timeout adapted to plan]
    D -->|No| F{Timer Trigger?}
    F -->|Yes| G[✅ Singleton check if multi-instance\n✅ IsPastDue checked\n✅ Timeout > estimated task duration]
    
    C & E & G --> H[For all functions]
    H --> I[✅ Structured logging\n✅ Application Insights configured\n✅ Managed Identity vs connection strings\n✅ Key Vault for secrets\n✅ IaC for infrastructure\n✅ Unit and integration tests]

Top 10 Common Pitfalls

#PitfallImpactSolution
1HttpClient not reusedSocket exhaustion, 503 errorsUse IHttpClientFactory
2Durable orchestrator violationsNon-deterministic behavior, hard-to-debug issuesFollow determinism rules
3Non-idempotent queue functionsDuplicates, corrupted dataCheck state before acting
4Secrets in App SettingsSecurity leakKey Vault References
5AuthorizationLevel.Anonymous in prodUnauthorized accessAlways use Function or higher
6No poison queue monitoringSilently lost messagesAlerts on poison queue size
7Timeout too short on ConsumptionInterrupted functionsCheck timeout vs actual duration
8No retry logicTransient failures unhandledPolly + Azure SDK built-in retry
9All functions in one AppGlobally coupled scalingSeparate by domain and scaling profile
10Cold starts ignoredSLA not metPremium plan or pre-warming

Glossary {#glossary}

TermDefinition
Active-ActiveMulti-region deployment where all regions handle traffic simultaneously
Active-PassiveMulti-region deployment where the secondary region is on standby (failover)
APIMAzure API Management – API gateway handling authentication, rate limiting, routing
Azure Front DoorAzure’s global CDN and load balancer for HTTP applications
Change FeedReal-time stream of changes in Cosmos DB (inserts and updates)
Circuit BreakerPolly pattern: cuts calls to a temporarily failing service
Cold StartStartup delay for a new Function instance after a period of inactivity
CQRSCommand Query Responsibility Segregation – separating reads from writes
Dead-letter QueueQueue for messages that could not be processed after N attempts
Distributed TracingCorrelation of logs and traces across multiple services/functions
Event GridAzure pub/sub event routing service
Event HubAzure high-performance event streaming service
Fan-out/Fan-inPattern of parallelization then aggregation of results
Flex ConsumptionHybrid hosting plan between Consumption and Premium
Geo-ReplicationAutomatic data replication across multiple Azure regions
IdempotencyProperty: running the same operation N times = same result as running it once
JitterRandom variation added to retry delays to avoid request storms
Managed IdentityAzure AD identity automatically managed for an Azure resource
Pub/SubPublish-Subscribe: decoupled messaging pattern
RBACRole-Based Access Control – Azure role-based access control
Request Unit (RU)Unit of throughput measurement in Azure Cosmos DB
SagaDistributed transaction pattern with compensation on failure
Service BusAzure enterprise messaging service with sessions, dead-letter, transactions
ShardPartition of a distributed database (Cosmos DB)
StatelessWithout state: each invocation is independent of previous ones
Task HubDurable Functions storage space for orchestration state

Official documentation: learn.microsoft.com/azure/azure-functions/functions-best-practices.


Search Terms

designing · serverless · architectures · azure · functions · microsoft · integration · configuration · pattern · architecture · patterns · application · availability · concurrency · distributed · dynamic · event · event-driven · high · hosting · http · load · pitfalls · plan

Interested in this course?

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