Azure Functions: Advanced Patterns
What Are Azure Functions
Azure Functions is Azure’s event-driven serverless compute service. You write functions in languages like JavaScript, Python, C#, Java, PowerShell, or custom handlers that respond to events such as HTTP requests, timers, messages in Storage Queues, or Service Bus events. Azure manages the servers, scaling, and operational infrastructure while you focus on business logic.
This guide covers advanced patterns and hosting decisions for production workloads. For foundational Azure Functions concepts, see Azure Functions Fundamentals.
What Problems Azure Functions Solves
Without Azure Functions:
- Event-driven workloads require you to manage infrastructure for variable demand
- Small, isolated functions require container or VM overhead
- Scaling up and down is manual or requires complex autoscaling rules
- Development and operations teams manage infrastructure as well as code
With Azure Functions:
- Automatic scaling based on demand (from zero to thousands of instances)
- Pay only for execution time, not idle infrastructure
- Built-in bindings for common Azure services eliminate boilerplate code
- Stateless execution model simplifies distributed systems
- Durable Functions add state management and orchestration capabilities
How Azure Functions Differs from AWS Lambda
Architects familiar with AWS Lambda should understand these key differences:
| Concept | AWS Lambda | Azure Functions |
|---|---|---|
| Stateful orchestration | Step Functions (separate service) | Durable Functions (built into Functions) |
| Cold start cost | Included in invocation cost | Depends on hosting plan |
| Custom runtimes | Layers, custom runtimes | Custom handlers with any language |
| Hosting models | Lambda is fully managed | Consumption, Flex Consumption, Premium, Dedicated plans |
| Scaling to zero | Automatic and free | Consumption and Flex Consumption only |
| Reserved capacity | Provisioned concurrency | Premium reserved capacity |
| VNet integration | Requires NAT Gateway for outbound | Premium and Dedicated plans; Flex Consumption with load balancer |
| Monitoring | CloudWatch | Application Insights integrated |
| Pricing model | Per 1M invocations + GB-seconds | Per GB-second + plan-based costs |
Durable Functions
What Durable Functions Provide
Durable Functions is an extension to Azure Functions that adds stateful orchestration, checkpointing, and long-running coordination capabilities. Without Durable Functions, Azure Functions are stateless and short-lived. Durable Functions maintain state across multiple function invocations and provide guarantees about execution order and failure handling. Features include automatic checkpointing, retry logic, and event sourcing.
Core Durable Functions Concepts
Orchestrator Functions: These are the conductors that coordinate your workflow. An orchestrator function defines the sequence of activities, handles branching logic, and manages state. Orchestrators must be deterministic (same inputs always produce the same sequence of operations) because they can be replayed from history if failures occur.
Activity Functions: These perform the actual work. An activity might call an API, write to a database, or send an email. Activity functions can be non-deterministic because they are not replayed. They execute once and their output is recorded in history.
Entity Functions: These maintain durable state scoped to a unique identifier. An entity function is like a microservice with built-in persistence. You send it messages, it updates its state, and other functions can query its current state. Entity functions are useful for maintaining counts, managing approval workflows, or tracking object lifecycle.
Client Functions: These initiate orchestrations. A client function (typically triggered by HTTP) calls an orchestrator and receives an instance ID and status check URL.
Orchestration Patterns
Function Chaining is the simplest pattern: one activity completes, then the next begins. Orchestrator code reads linearly like imperative programming.
public static async Task<int> RunOrchestrator(
IDurableOrchestrationContext context)
{
var result1 = await context.CallActivityAsync<int>("Activity1", null);
var result2 = await context.CallActivityAsync<int>("Activity2", result1);
var result3 = await context.CallActivityAsync<int>("Activity3", result2);
return result3;
}
Fan-Out/Fan-In executes multiple activities in parallel, then waits for all to complete before proceeding. This pattern is valuable for parallel workflows like processing multiple items in a batch.
public static async Task RunOrchestrator(
IDurableOrchestrationContext context,
string[] items)
{
var tasks = items.Select(item =>
context.CallActivityAsync("ProcessItem", item)
).ToArray();
await Task.WhenAll(tasks);
await context.CallActivityAsync("Consolidate", null);
}
Async HTTP API patterns handle long-running operations that clients want to monitor. The orchestrator starts, returns immediately with a status check URL, and the client polls for completion.
[FunctionName("StartLongRunningWork")]
public static async Task<IActionResult> HttpStart(
[HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestMessage req,
[DurableClient] IDurableOrchestrationClient client)
{
var instanceId = await client.StartNewAsync("LongRunningOrchestrator");
return client.CreateCheckStatusResponse(req, instanceId);
}
Monitoring patterns periodically check a condition until it becomes true, then proceed. This is useful for polling external systems.
public static async Task RunOrchestrator(
IDurableOrchestrationContext context,
string jobId)
{
var retryOptions = new RetryOptions(
firstRetryInterval: TimeSpan.FromSeconds(30),
maxNumberOfAttempts: int.MaxValue)
{
Handle = ex => ex is TimeoutException
};
while (true)
{
var status = await context.CallActivityWithRetryAsync(
"CheckJobStatus", retryOptions, jobId);
if (status.IsComplete) break;
var nextCheck = context.CurrentUtcDateTime.AddSeconds(30);
await context.CreateTimer(nextCheck, CancellationToken.None);
}
}
Human Interaction patterns pause orchestration waiting for external approval or input. The orchestrator waits for a specific event before proceeding, allowing humans to interact with the workflow.
public static async Task RunOrchestrator(
IDurableOrchestrationContext context,
string approvalId)
{
await context.CallActivityAsync("NotifyApprover", approvalId);
using (var cts = new CancellationTokenSource())
{
var approvalTask = context.WaitForExternalEvent("ApprovalReceived");
var timeoutTask = context.CreateTimer(
context.CurrentUtcDateTime.AddDays(1),
cts.Token);
if (approvalTask == await Task.WhenAny(approvalTask, timeoutTask))
{
await context.CallActivityAsync("ProcessApproved", approvalId);
}
else
{
cts.Cancel();
await context.CallActivityAsync("ProcessRejected", approvalId);
}
}
}
Sub-Orchestrations allow an orchestrator to call another orchestrator, creating hierarchical workflows. This is useful for breaking complex workflows into manageable pieces.
public static async Task RunOrchestrator(
IDurableOrchestrationContext context)
{
var result1 = await context.CallSubOrchestratorAsync("SubOrchestrator1", null);
var result2 = await context.CallSubOrchestratorAsync("SubOrchestrator2", result1);
return result2;
}
When Durable Functions Become Critical
Durable Functions shine when you need to coordinate multiple services across long periods (minutes to days), handle failures with automatic retry, or maintain state across distributed operations. Without Durable Functions, you would implement all of this manually using databases, timers, and polling logic.
Durable Functions State Management
Durable Functions use event sourcing internally. Every action in an orchestrator (calling an activity, waiting for a timer, receiving an external event) is recorded as an event in the orchestration history. If the orchestrator fails and restarts, it replays the history to recover its state. This automatic replay is why orchestrators must be deterministic. The same history must always produce the same next action.
Hosting Models
Azure Functions provides four hosting options, each with different scaling behaviors, cost structures, and operational characteristics.
Consumption Plan
What it provides: Automatic scaling from zero, you pay only for execution time, functions scale up rapidly in response to demand.
Scaling behavior:
- Functions start from zero instances
- Azure adds instances as demand increases (seconds to minutes latency for new instances)
- Idle instances scale to zero after a period of inactivity
- Peak scale-out capability is limited (typically hundreds of concurrent executions, not thousands)
Cost model: Billed per GB-second of execution plus a monthly free grant.
Best for: Development, testing, and event-driven workloads with unpredictable demand where cost is the primary concern.
Trade-offs:
- Cold starts on the first invocation after scale-to-zero
- Limited to short function execution times
- Potential function timeout under high load (if all instances are busy)
- No guaranteed scale-out capacity
Flex Consumption Plan
Flex Consumption is a newer option combining benefits of Consumption and Premium plans. It scales from zero like Consumption but offers faster cold starts, higher concurrency, and predictable performance like Premium.
Scaling behavior:
- Scales from zero to very high concurrency (thousands of concurrent executions)
- Cold starts are significantly reduced compared to Consumption
- Better warm start performance than Consumption through smarter pre-warming
Cost model: Pay per GB-second like Consumption, but with higher rates due to reserved infrastructure.
Best for: Workloads that need scale-to-zero cost model but cannot tolerate Consumption’s cold start latency or scale-out delays. Good for variable workloads with occasional traffic spikes that require rapid scaling.
Trade-offs:
- Moderately higher cost than Consumption for the same execution time
- Requires VNet integration setup if you need database or service connectivity inside a VNet
- Newer offering with evolving feature set
Premium Plan
What it provides: Pre-warmed instances, guaranteed concurrency, VNet integration, and longer execution times.
Scaling behavior:
- Maintains at least one instance warm at all times (no scale-to-zero)
- Adds instances as demand increases, but faster than Consumption
- You reserve instance count (P1V2, P2V2, P3V2 sizes)
- Elastic scale-out handles spikes beyond reserved capacity
Cost model: Fixed hourly rate for reserved instances plus overage charges for scale-out beyond reserved capacity.
Best for: Production workloads requiring predictable performance, VNet connectivity, or longer execution times. Suitable for businesses where cold start latency is unacceptable.
Trade-offs:
- Higher baseline cost (pre-warmed instances are always running)
- Not suitable for highly variable workloads where Consumption would be cheaper
- Still requires cost management (overage scale-out can become expensive under sustained high load)
Dedicated Plan (App Service Plan)
What it provides: Full control over instance sizing and count. Functions run on the same infrastructure as App Service. You manage scaling through VM scale sets.
Scaling behavior:
- You choose instance size and count manually, or configure App Service autoscale rules
- No automatic scale-to-zero
- You scale by updating VM count, giving you precise control
Cost model: Hourly rate for the App Service plan (based on instance size), regardless of function execution.
Best for: High-volume workloads that would be expensive on consumption-based plans, environments where you already have App Service infrastructure, or workloads requiring custom Windows features or specific runtime versions.
Trade-offs:
- Highest operational overhead (you manage scaling rules)
- Expensive for variable workloads (you pay for all instances regardless of usage)
- Best for steady-state, predictable load
Container-Hosted Functions
Functions can run in containers on App Service or Azure Container Apps. Container Apps combines managed container orchestration with scale-to-zero capability using KEDA (Kubernetes Event Scaling for Autoscaling).
When to use containers:
- You need custom runtime environments not supported by built-in runtimes
- You have existing Docker images to repurpose
- You want KEDA-based scaling with custom metrics
- You prefer container-based deployment pipelines
Scaling with KEDA: KEDA (Kubernetes-based Event Driven Autoscaling) allows Container Apps to scale based on Azure service events (Storage Queue depth, Service Bus queue length) without needing pre-configured triggers. This enables fine-grained scaling behavior in containerized environments.
Hosting Plan Comparison Table
| Aspect | Consumption | Flex Consumption | Premium | Dedicated | Containers |
|---|---|---|---|---|---|
| Scale to zero | Yes | Yes | No | No | No (typically) |
| Baseline cost | Pay-per-use | Pay-per-use | Hourly for reserved | Hourly for VMs | Depends on plan |
| Cold start | High (~5-10s) | Low (~1-2s) | None (pre-warmed) | None | Depends on image |
| Max concurrency | Hundreds | Thousands | Thousands | Limited by VM count | Configurable |
| Execution time limit | 5-10 minutes | 30 minutes | 60 minutes | Unlimited | Unlimited |
| VNet integration | Limited | Via load balancer | Full | Full | Full |
| Cost for spiky load | Cheapest | Moderate | Expensive | Expensive | Varies |
| Cost for steady load | Expensive | Moderate | Moderate | Moderate-Cheap | Varies |
Custom Handlers and Polyglot Runtimes
What Custom Handlers Enable
Custom handlers allow you to write Azure Functions in any language by implementing a simple HTTP server. Azure Functions acts as a proxy, forwarding events to your HTTP server and returning responses. This enables languages like Rust, Go, Kotlin, or Swift to run as Azure Functions without waiting for native runtime support.
How Custom Handlers Work
- You write an HTTP server in your language of choice that listens on a port
- The Azure Functions runtime forwards each trigger event as an HTTP POST request to your server
- Your server processes the request and returns the result
- The runtime handles the response and manages function lifecycle
Basic structure:
- Request body contains trigger data (HTTP body for HTTP triggers, queue message for Queue triggers, etc.)
- Request headers include metadata about the invocation
- Your server responds with status code and output data
- The runtime handles marshalling between Azure services and HTTP
When to Use Custom Handlers
Use custom handlers when:
- You need a language not supported by built-in runtimes (Rust for performance-critical functions, Go for rapid scaling)
- You have existing HTTP microservices to expose as Functions
- You want to reuse libraries only available in a specific language
- You need fine-grained control over function behavior
Avoid custom handlers when:
- Built-in runtimes (C#, JavaScript, Python) provide what you need (startup overhead is higher)
- Simplicity matters more than language choice
- The HTTP forwarding latency is unacceptable (typically milliseconds overhead)
Deployment Strategies
Blue-Green Deployment
Azure Functions supports deployment slots on Premium and Dedicated plans. Slots are staging environments where you deploy a new version before swapping it live.
How it works:
- Deploy new function version to a staging slot
- Test the staging slot (invoke functions, verify behavior)
- Swap traffic from production slot to staging slot (near-instantaneous)
- If issues occur, swap back to the previous version
Benefits:
- Zero-downtime deployments
- Ability to test before swapping
- Instant rollback if something breaks
Trade-offs:
- Slots are only available on Premium and Dedicated plans (not Consumption)
- Swap operation still involves a brief connection warm-up
Zip Deploy
Zip deployment packages your function code as a zip file and uploads it directly to Azure. This is the fastest way to deploy functions from CI/CD pipelines.
Container Deployment
For Functions running in containers, deployment follows standard container image workflows:
- Build and push image to a container registry
- Update the Function App to pull from the new image tag
- Container Apps or App Service pulls the new image and restarts
Container deployment integrates naturally with existing CI/CD pipelines using Docker.
Concurrency, Throttling, and Performance
Instance Concurrency
Each Azure Functions instance can execute multiple functions concurrently. The number of concurrent executions depends on the hosting plan and function language.
C# has explicit concurrency limits: By default, a single instance can execute one async function at a time (orchestrator pattern). You configure functionTimeout and maxConcurrentRequests in host.json to allow more parallel work.
JavaScript and Python are naturally concurrent: Due to their async/await models, instances naturally handle multiple concurrent operations per instance.
Throttling in Service Connections
When your functions call external services through bindings (Service Bus, Storage, Cosmos DB), Azure applies throttling to prevent overwhelming the service. Service Bus clients are throttled at 1000 concurrent operations per instance by default. Storage has rate limits per account.
How to handle throttling:
- Use backoff-retry patterns with exponential backoff (Durable Functions provide built-in retry)
- Partition work across multiple services (multiple Service Bus namespaces, multiple Storage accounts)
- Use autoscaling with target-based metrics (scale down when throttling signals appear)
Cold Start Mitigation
Cold starts occur when an instance needs to start the runtime and load your function code. This happens on Consumption plan when all instances are busy, or when scaling from zero.
Mitigation strategies:
- Premium plan: Eliminates cold starts by keeping instances warm
- Flex Consumption: Reduces cold starts through better pre-warming
- Application Insights warm-up: Use a timer function to periodically invoke your function before the application would otherwise go idle (though this adds cost)
- Code optimization: Lazy-load dependencies, reduce function startup code, compile functions ahead of time in C#
Error Handling and Retry Policies
Built-in Retry for Bindings
Bindings for Azure services (Service Bus, Storage, Event Hubs) have built-in retry logic. If a trigger fires and your function fails, the runtime automatically retries the function with exponential backoff.
Retry behavior varies by trigger:
- Service Bus: Retries with exponential backoff up to 10 times (configurable)
- Storage Queue: Retries up to 5 times (after which the message goes to a poison queue)
- Event Hubs: No automatic retry (your function must handle failures)
Durable Functions Retry
Durable Functions provide explicit retry policies for activity functions and sub-orchestrators.
var retryOptions = new RetryOptions(
firstRetryInterval: TimeSpan.FromSeconds(1),
maxNumberOfAttempts: 5)
{
BackoffCoefficient = 2.0,
Handle = ex => ex is not ArgumentException
};
var result = await context.CallActivityWithRetryAsync(
"MyActivity", retryOptions, input);
Retry configuration:
firstRetryInterval: Delay before first retrymaxNumberOfAttempts: Maximum retry attemptsBackoffCoefficient: Multiplier for delay between retries (exponential backoff)Handle: Predicate to determine if an exception should be retried
Dead-Letter Patterns
When retries exhaust, messages move to dead-letter storage for manual inspection.
Implementing dead-lettering:
- Service Bus has built-in dead-letter queues for messages that fail processing after max delivery count
- For custom logic, catch exceptions, log details, and store failed items in a dead-letter table or queue
- Periodically review dead-letter storage and manually retry or fix root causes
Advanced Bindings and Triggers
Custom Bindings
Azure Functions provides binding extensions for common Azure services. You can also build custom bindings as NuGet packages for domain-specific logic or third-party services.
When to build custom bindings:
- You have domain-specific logic that many functions need (e.g., authorization, data transformation)
- You want to abstract away boilerplate code
- You have internal services that need Function integration
Event Grid Triggers
Event Grid triggers respond to system and custom events. Event Grid provides publish-subscribe messaging with filtering, dead-lettering, and retry.
Event Grid characteristics:
- Push model (Event Grid pushes events to your Function instead of polling)
- Automatic retry with exponential backoff
- Built-in dead-lettering for events that fail after retries
- Filtering by event type, subject, or custom properties
Service Bus and Queue Triggers
Service Bus triggers provide message-based coordination with processing guarantees.
Service Bus features:
- Message ordering (partitioned queues)
- Dead-letter queue for failed messages
- Session state for maintaining per-message context
- Delayed message delivery (schedule processing)
When to use Service Bus vs Storage Queue:
- Service Bus: Message ordering, sessions, advanced routing, compliance-sensitive workloads
- Storage Queue: Simple pub-sub, cost-sensitive, high throughput, short TTL messages
Kafka Triggers
Azure Functions supports Kafka triggers and bindings for consuming from Apache Kafka topics on Azure Event Hubs or self-managed Kafka clusters.
Kafka characteristics:
- Parallel processing across partitions (one instance per partition)
- Exactly-once delivery semantics with offset management
- Batch processing support
Stateful vs Stateless Design
Stateless Functions (Default)
Stateless functions do not maintain state between invocations. Each invocation is independent.
Stateless advantages:
- Simple to understand and debug
- Scale horizontally without coordination
- No distributed state consistency issues
Stateless disadvantages:
- External storage required to maintain state
- Multiple functions need to coordinate through messages
Consider a stateless transaction processor that receives messages from a queue, processes them, and writes results to a database. If the function crashes, the message is retried from the queue.
Stateful Design with Durable Functions
Durable Functions maintain state across invocations using event history.
Stateful advantages:
- Orchestrator state is automatically recovered from history
- No need for external state storage for workflow state
- Clear, imperative code that looks like single-threaded programming
Stateful disadvantages:
- Orchestrators must be deterministic (same input = same execution flow)
- History grows over time (though cleanup policies can manage this)
- More complex than stateless
Consider a stateful approval workflow that uses an orchestrator to maintain workflow state across approval steps, timeouts, and human decisions. The orchestrator automatically resumes from the last checkpoint if it fails.
Entity Functions for Shared State
Entity functions maintain durable state accessible to other functions. Unlike orchestrators, entities can receive multiple messages and maintain mutable state.
Entity use cases:
- Counters (increment/decrement with ordering guarantees)
- Approval workflows (track who approved, when)
- Resource locks (prevent concurrent modification)
- Session state (store per-user context)
[FunctionName("Counter")]
public static async Task Counter(
[EntityFunctionInput] IDurableEntityContext ctx)
{
var state = ctx.State<int>();
switch (ctx.OperationName)
{
case "increment":
state++;
break;
case "decrement":
state--;
break;
case "get":
ctx.SetResult(state);
break;
}
}
Performance Optimization
Code Optimization
Dependency injection: Use Azure Functions dependency injection to avoid repeatedly instantiating expensive objects (HTTP clients, database connections) on every invocation.
public class MyFunction
{
private readonly HttpClient _httpClient;
public MyFunction(HttpClient httpClient)
{
_httpClient = httpClient;
}
[FunctionName("MyFunction")]
public async Task Run([HttpTrigger] HttpRequest req)
{
// _httpClient is reused across invocations
var response = await _httpClient.GetAsync("...");
}
}
Lazy initialization: Load large dependencies (machine learning models, large datasets) only when needed, not on every invocation.
Async patterns: Use async/await throughout to avoid blocking threads and waste resources.
Monitoring and Performance Analysis
Application Insights integration is built into Azure Functions and tracks execution time, failures, and dependencies.
Key metrics to monitor:
- Function execution time (identify slow operations)
- Failure rates (identify unstable dependencies)
- Dependency call counts (identify unintended N+1 calls)
- Cold start frequency and duration
Comparison with AWS and Google Cloud
vs AWS Lambda and Step Functions
| Aspect | Azure Functions + Durable | AWS Lambda + Step Functions |
|---|---|---|
| Orchestration | Built into Functions | Separate Step Functions service |
| Hosting models | Consumption, Premium, Dedicated | Lambda only |
| Language support | C#, JavaScript, Python, Java, PowerShell, custom handlers | Node, Python, Java, Go, C# (via custom runtime) |
| State management | Event sourcing in Durable Functions | Step Functions state machine definitions |
| Cost model | GB-second based | Per-invocation + GB-second |
| Scaling | Automatic (plan-dependent) | Automatic |
| VNet integration | Premium/Dedicated/Flex with load balancer | Requires additional configuration |
Rough feature parity: Durable Functions orchestrators are equivalent to Step Functions state machines, activity functions to Lambda, and entity functions to DynamoDB with built-in locks.
vs Google Cloud Functions
| Aspect | Azure Functions | Google Cloud Functions |
|---|---|---|
| Pricing | GB-second based | Per invocation + GB-second (lower invocation cost) |
| Orchestration | Durable Functions built-in | Cloud Workflows (separate service) |
| Cold start | Consumption: moderate; Premium: none | Generally faster cold starts |
| VNet integration | Limited on Consumption; full on Premium | Native VPC integration |
| Memory scaling | Per hosting plan | Per function (128 MB to 16 GB) |
Common Pitfalls
Pitfall 1: Non-Deterministic Orchestrator Code
Problem: Writing orchestrators that include non-deterministic operations like DateTime.Now or random numbers.
Result: When the orchestrator replays from history, the non-deterministic code produces different results, breaking the workflow state.
Solution: Use context.CurrentUtcDateTime instead of DateTime.UtcNow, avoid random operations in orchestrators, and keep non-deterministic logic in activity functions.
Pitfall 2: Choosing Consumption Plan for Unpredictable Latency Workloads
Problem: Deploying on Consumption plan for latency-sensitive functions without accounting for cold starts and scale-out delays.
Result: Users experience variable response times (100ms warm start vs 5-10s cold start), violating SLA expectations.
Solution: Use Premium or Flex Consumption plans for latency-sensitive workloads. Accept higher baseline cost for predictable performance.
Pitfall 3: Ignoring Throttling Limits on Azure Services
Problem: Functions scale up faster than downstream services (Service Bus, SQL Database) can handle, causing cascading failures.
Result: Functions timeout waiting for throttled service calls, messages pile up in trigger queues, and the function app becomes unresponsive.
Solution: Implement backoff-retry logic, use Durable Functions for long-running operations, partition work across multiple service instances, and monitor throttling metrics.
Pitfall 4: Storing Large State in Orchestrator History
Problem: Passing large objects as input to orchestrators or storing large results in activity outputs.
Result: History grows rapidly, consuming storage quota and degrading performance.
Solution: Store large data in external storage (blob, database) and pass references (IDs, URLs) through orchestrations instead.
Pitfall 5: Not Planning for Dead-Letter Handling
Problem: Functions fail intermittently, messages exhaust retries, and dead-letter messages accumulate without investigation.
Result: Work is silently lost; root causes remain undiagnosed.
Solution: Monitor dead-letter queues, implement automated dead-letter processing (log, alert, store for review), periodically review and retry failed messages.
Pitfall 6: Assuming All Hosting Plans Have Same Behavior
Problem: Developing on Consumption plan expecting scale-to-zero, then deploying to Premium plan without adjusting expectations.
Result: Costs exceed budget due to always-on instances, or performance unexpectedly drops when scaling expectations change.
Solution: Choose the hosting plan early based on load characteristics and performance requirements, and test on the actual target plan.
Key Takeaways
-
Durable Functions add orchestration and state management to Azure Functions. Orchestrators define workflows, activity functions do the work, and entity functions maintain shared state. This is equivalent to AWS Step Functions but integrated directly into Azure Functions.
-
Choose the hosting plan based on load and cost priorities. Consumption offers lowest cost for variable workloads. Premium offers predictable performance for production. Flex Consumption bridges both with better cold starts than Consumption.
-
Custom handlers enable any language. If your primary language isn’t natively supported, custom handlers provide a path forward, albeit with higher startup overhead.
-
Concurrency and throttling require careful management. Azure services throttle heavily under load. Use backoff-retry, partition work, and scale horizontally to handle throttling gracefully.
-
Stateless functions are the default; use Durable Functions for workflows. Most functions should be stateless and independently scalable. Durable Functions are for coordinating multi-step workflows.
-
Event sourcing powers Durable Functions state recovery. Orchestrators must be deterministic because they replay from history on restart. This enables automatic state recovery without explicit state storage.
-
Blue-green deployments with slots enable zero-downtime updates. Slots are available only on Premium and Dedicated plans, but they provide the ability to test before swapping.
-
Monitor cold starts, failures, and dependency performance. Application Insights is integrated and should be your primary diagnostic tool.
-
Container deployment enables custom runtime environments. Functions can run in containers on App Service or Container Apps, enabling custom languages and libraries via custom handlers.
-
Dead-letter handling is essential for reliability. Messages that fail retries must be captured, logged, and reviewed to prevent silent failures.
Found this guide helpful? Share it with your team:
Share on LinkedIn