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
- What is Serverless?
- Azure Functions – Overview
- Hosting Plans
- Triggers and Bindings
- Practical Use Cases
- Developing Your First Azure Function
- Event-Driven Architecture
- Durable Functions – Introduction
- Integration with Logic Apps
- Development Options and Tools
- Best Practices for Beginners
- 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
| Aspect | Traditional (VM) | Containers (AKS) | Serverless (Azure Functions) |
|---|---|---|---|
| Infrastructure | You manage everything (OS, runtime, patches) | You manage containers | Azure manages everything |
| Scalability | Manual or configured auto-scale | Kubernetes HPA/KEDA | Automatic, scale to zero |
| Payment | Instance always running | Pods always running | Per execution only |
| Startup | Application always active | Container always active | Event-driven (cold start possible) |
| Time-to-market | Slow (infra to configure) | Medium (Docker + K8s) | Fast (a few minutes) |
| Developer focus | Infra + code | Docker + code | Code 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
| Characteristic | Description | Benefit |
|---|---|---|
| No Infrastructure Management | Azure manages VMs, OS, runtime, patches | Focus on business code |
| Auto-Scaling | Automatic scale from 0 to 200+ instances | No manual configuration |
| Consumption Pricing | 1 million free executions/month | Minimal or zero cost for small projects |
| Event-Driven | Triggered by real events | Maximum responsiveness |
| Stateless | Each invocation is isolated | Infinite 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:
- Data processing: transform CSV files upon upload to Azure Blob Storage
- System integration: connect two services without a native connector (Event Hub → Cosmos DB)
- Lightweight APIs: REST microservices with unpredictable load
- Webhooks: receive notifications from GitHub, Stripe, Slack, etc.
- Scheduled tasks: database cleanup every night
- IoT: process sensor data in real time
- Notifications: send emails/SMS upon events
- 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
| Language | Runtime | Recommended Model | Support |
|---|---|---|---|
| C# | .NET 8/9 | Isolated Worker | ✅ Full |
| JavaScript | Node.js 18/20 | v4 Model | ✅ Full |
| TypeScript | Node.js 18/20 | v4 Model | ✅ Full |
| Python | Python 3.11/3.12 | v2 Model | ✅ Full |
| Java | Java 11/17/21 | - | ✅ Full |
| PowerShell | PS 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]
| Plan | Average Cost | Cold Start | Timeout | VNet | Memory |
|---|---|---|---|---|---|
| Consumption | Very low (often $0) | Yes (1-10s) | 10 min | ❌ | 1.5 GB |
| Flex Consumption | Low | Configurable | 60 min | ✅ | 512 MB - 4 GB |
| Premium EP1 | ~$150/month | No | Unlimited | ✅ | 3.5 GB |
| Premium EP2 | ~$300/month | No | Unlimited | ✅ | 7 GB |
| Dedicated B1 | ~$13/month | No | Unlimited | ✅ | 1.75 GB |
| Dedicated P1v3 | ~$140/month | No | Unlimited | ✅ | 8 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
| Trigger | Trigger Event | Example Use Case |
|---|---|---|
| HTTP | HTTP/HTTPS request | REST API, Stripe webhook, web form |
| Timer | Cron schedule | Nightly report, DB cleanup, batch |
| Storage Queue | Message in Azure Queue | Async processing, decoupling |
| Service Bus | Topic/queue message | Enterprise messaging, pub/sub |
| Event Grid | Azure or custom event | React to Azure resource changes |
| Cosmos DB | Change Feed | Synchronization, cache invalidation |
| Blob Storage | Blob created/modified | Image processing, CSV transformation |
| Event Hubs | IoT or streaming event | IoT processing, real-time analytics |
| SQL | SQL Server change | CDC, 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
| Type | Direction | Description | Example |
|---|---|---|---|
| Trigger Binding | ← | Triggers + provides data | QueueTrigger delivers the message |
| Input Binding | ← | Reads additional data | BlobInput reads a file |
| Output Binding | → | Writes data to a service | QueueOutput 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:
| Tool | Purpose | Installation |
|---|---|---|
| .NET SDK 8.0+ | Compile C# code | dotnet.microsoft.com |
| Azure Functions Core Tools v4 | Run locally | npm install -g azure-functions-core-tools@4 |
| Visual Studio Code | Recommended IDE | code.visualstudio.com |
| Azure Functions Extension (VSCode) | Templates and debugging | VS Code Marketplace |
| Azurite | Azure Storage emulator | npm 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
| Type | Role | Allowed Code |
|---|---|---|
| Starter Function | Starts the orchestration | Everything (HTTP, Queue, Timer, etc.) |
| Orchestrator Function | Coordinates the workflow | DETERMINISTIC code only! |
| Activity Function | Does the actual work | Everything (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:
| Scenario | Logic Apps | Azure Function |
|---|---|---|
| Visual orchestration | ✅ Graphical designer | Too complex to code |
| Complex business logic | Too verbose | ✅ Precise C# code |
| 400+ service connectors | ✅ Native | Must be coded |
| High-frequency performance | Limited | ✅ Automatic scalability |
| Data transformation | Limited | ✅ Free code |
Module 10 – Development Options and Tools {#module-10}
Development Environment Comparison
| Option | Advantages | Disadvantages | Recommendation |
|---|---|---|---|
| VS Code + Extension | Cross-platform, free, templates | Less powerful than VS | ✅ Recommended for beginners |
| Visual Studio 2022 | Complete, advanced IntelliSense | Windows only | ✅ For C# experts |
| Azure Portal | No installation | Limited, not for production | ⚠️ Testing only |
| CLI + Azure Cloud Shell | Accessible everywhere, no install | Basic interface | 🟡 Quick demos |
| GitHub Codespaces | Dev env in the cloud | Paid | 🟡 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
| # | Practice | Why |
|---|---|---|
| 1 | Small and focused functions | Fewer dependencies = faster startup |
| 2 | No Thread.Sleep() | Use Task.Delay() (async) |
| 3 | Always use async/await | Never block threads |
| 4 | Dependency injection via Program.cs | Testable and maintainable code |
| 5 | Logging with ILogger | Structured logs in Application Insights |
| 6 | No secrets in code | Use App Settings + Key Vault |
| 7 | Timeout appropriate to the plan | Consumption max 10 min |
| 8 | Idempotence for queue functions | Messages can be replayed |
| 9 | Application Insights configured | Observability in production |
| 10 | Unit tests | Quality 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}
| Term | Definition |
|---|---|
| Binding | Declarative connection to a service (Input = read, Output = write) |
| Cold Start | Delay during the first startup of an inactive Function instance |
| Consumption Plan | Azure Functions serverless plan, billed per execution |
| Cron Expression | Expression format for Timer Trigger schedules (6 fields) |
| DefaultAzureCredential | Azure SDK class for automatic authentication |
| Durable Functions | Azure Functions extension for stateful workflows |
| Event-Driven | Reactive architecture triggered by events |
| Function App | Deployment unit containing one or more Azure Functions |
| ILogger | Standard .NET interface for logs (injected into Functions) |
| Isolated Worker Model | C# model: Functions in a separate .NET process from the runtime |
| Microservice | Independent service focused on a single responsibility |
| Orchestrator | Durable function that coordinates the workflow |
| Serverless | Infrastructure managed by Azure, billed per usage |
| Stateless | No persistent state between invocations |
| Trigger | Event triggering the execution of an Azure Function |
| Webhook | HTTP 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:
| Aspect | Serverless | Traditional |
|---|---|---|
| Infrastructure | Managed by Azure | You manage (VMs, OS, patches) |
| Scalability | Automatic (0 → ∞) | Manual or configured auto-scale |
| Payment | Per execution (consumption) | For the instance even if idle |
| Startup | Event-driven | Always 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:
| Plan | Cold Start | Scale | VNet | Best For |
|---|---|---|---|---|
| Consumption | Yes | 0→200 instances | No | True serverless, sporadic |
| Flex Consumption | Reduced | Configurable | ✅ Yes | VNet required + flexible scale |
| Premium | No (pre-warmed) | Configurable | ✅ Yes | Performance, no cold start |
| Dedicated (App Service) | No | Manual/auto | ✅ Yes | Constant usage |
| Container Apps | Per config | KEDA-based | ✅ Yes | Kubernetes 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:
| Type | Role |
|---|---|
| Client Function | Starts the orchestration, can query state |
| Orchestrator Function | Defines the workflow (steps, conditions, fan-out) |
| Activity Function | Atomic 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
| Concept | Description |
|---|---|
| Serverless | Managed infrastructure, auto-scale, pay-per-execution |
| Function App | Logical container for grouping Azure Functions |
| Trigger | Starts execution (1 per function) |
| Binding | Declarative connection to services (input/output) |
| Consumption Plan | True serverless, cold start, scale 0→200 |
| Premium Plan | Pre-warmed, no cold start, VNet |
| NCrontab | Cron syntax for Timer Trigger |
| Durable Functions | Azure Functions with state for long-running workflows |
| Orchestrator | Defines the workflow in Durable Functions |
| Activity Function | Atomic unit of work in Durable Functions |
| Fan-out/Fan-in | Launch tasks in parallel, wait for all |
| Human Interaction | Wait 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