Azure Functions Guide — Triggers, Bindings, Durable Functions, and Python Examples
Azure Functions is Microsoft’s serverless compute platform that lets you run event-driven code without managing infrastructure, with built-in bindings for seamless Azure service integration.
What You’ll Learn
By the end of this tutorial, you’ll understand Azure Functions triggers and bindings, the differences between consumption and premium plans, Durable Functions for stateful workflows, and how to build HTTP-triggered functions in Python.
Why Azure Functions Matters
Azure Functions provides deep integration with the Microsoft ecosystem — Office 365, Dynamics, Teams, and Azure services. If your organization uses Microsoft tools, Azure Functions enables automation and event processing without leaving the stack. DodaTech uses Azure Functions for processing DodaZIP license validation requests and webhook event processing.
Azure Functions Learning Path
flowchart LR
A[Cloud Basics] --> B[Azure]
B --> C[Azure Functions]
C --> D{You Are Here}
D --> E[Triggers]
D --> F[Bindings]
D --> G[Durable Functions]
E --> H[HTTP]
E --> I[Blob Storage]
E --> J[Queue Storage]
What Are Azure Functions?
Think of Azure Functions like a mailroom in a large office building. When a package arrives (trigger), the mailroom processes it automatically — sorting, logging, routing (bindings). You don’t specify which mailroom worker does the job or when they work. The system handles staffing (scaling) automatically.
If a package needs multiple steps? Durable Functions chains them together: receive → log → sort → deliver. One step can wait for the previous without managing state manually.
Triggers and Bindings
Triggers start function execution. Bindings connect to data declaratively.
| Trigger | When It Fires | Use Case |
|---|---|---|
| HTTP | HTTP request received | REST API endpoints, webhooks |
| Timer | Scheduled interval | Batch processing, health checks |
| Blob Storage | Blob created/updated | Image processing, file ingestion |
| Queue Storage | Message added to queue | Background job processing |
| Cosmos DB | Document changed | Real-time data sync |
| Event Grid | Azure service event | Multi-service orchestration |
| Service Bus | Queue/topic message | Enterprise messaging |
# http_trigger.py
# Azure Function with HTTP trigger + input/output bindings
import azure.functions as func
import json
import logging
from datetime import datetime
def main(req: func.HttpRequest,
inputblob: func.InputStream,
outputblob: func.Out[str]) -> func.HttpResponse:
"""
HTTP-triggered function that:
- Accepts a name in the request body
- Reads a template from Blob Storage (input binding)
- Writes a personalized message (output binding)
- Returns the result as HTTP response
"""
logging.info('Python HTTP trigger function processed a request.')
# Get name from request
try:
req_body = req.get_json()
name = req_body.get('name', 'World')
except ValueError:
name = req.params.get('name', 'World')
# Read template from blob (input binding)
template = "Hello, {name}! This message was generated at {time}."
if inputblob:
template = inputblob.read().decode('utf-8')
# Generate message
message = template.format(name=name, time=datetime.now().isoformat())
# Write to blob (output binding)
outputblob.set(message)
logging.info(f'Generated message for {name}')
return func.HttpResponse(
json.dumps({"message": message, "status": "success"}),
mimetype="application/json",
status_code=200
)# functions_simulator.py
# Simulate Azure Functions locally for learning
import time
import json
from datetime import datetime
from collections import defaultdict
class FunctionRuntime:
def __init__(self, plan="Consumption", memory_mb=128):
self.plan = plan
self.memory = memory_mb
self.stats = defaultdict(int)
def http_trigger(self, method, path, body=None):
"""Simulate an HTTP-triggered function."""
self.stats["total_invocations"] += 1
start = time.time()
# Simulate cold start
if self.stats["total_invocations"] == 1:
penalty = 0.3 if self.plan == "Consumption" else 0.05
time.sleep(penalty)
# Process request
name = "World"
if body:
try:
data = json.loads(body)
name = data.get("name", name)
except json.JSONDecodeError:
pass
result = {
"message": f"Hello, {name}! Processed at {datetime.now().isoformat()}",
"method": method,
"path": path,
"plan": self.plan,
}
duration = (time.time() - start) * 1000
self.stats["total_duration_ms"] += duration
print(f"[{self.plan}] HTTP {method} {path} → '{result['message']}' ({duration:.0f}ms)")
return result
def blob_trigger(self, container, blob_name):
"""Simulate a Blob Storage-triggered function."""
self.stats["total_invocations"] += 1
self.stats["blob_processed"] += 1
print(f"[BlobTrigger] Processing {container}/{blob_name}...")
time.sleep(0.1)
print(f"[BlobTrigger] ✓ Resized thumbnail for {blob_name}")
def stats_report(self):
avg_duration = self.stats["total_duration_ms"] / max(self.stats["total_invocations"], 1)
print(f"\n=== Function Runtime Stats ===")
print(f" Plan: {self.plan}")
print(f" Total invocations: {self.stats['total_invocations']}")
print(f" Average duration: {avg_duration:.0f}ms")
print(f" Blobs processed: {self.stats.get('blob_processed', 0)}")
# Demo
print("=== Azure Functions Simulation ===\n")
runtime = FunctionRuntime(plan="Consumption")
runtime.http_trigger("GET", "/api/hello")
runtime.http_trigger("POST", "/api/hello", '{"name": "DodaTech"}')
runtime.http_trigger("GET", "/api/health")
runtime.blob_trigger("uploads", "photos/selfie.jpg")
runtime.blob_trigger("uploads", "docs/report.pdf")
runtime.stats_report()Expected output:
=== Azure Functions Simulation ===
[Consumption] HTTP GET /api/hello → 'Hello, World! Processed at ...' (420ms)
[Consumption] HTTP POST /api/hello → 'Hello, DodaTech! Processed at ...' (5ms)
[Consumption] HTTP GET /api/health → 'Hello, World! Processed at ...' (3ms)
[BlobTrigger] Processing uploads/photos/selfie.jpg...
[BlobTrigger] ✓ Resized thumbnail for selfie.jpg
[BlobTrigger] Processing uploads/docs/report.pdf...
[BlobTrigger] ✓ Resized thumbnail for report.pdf
=== Function Runtime Stats ===
Plan: Consumption
Total invocations: 5
Average duration: 86ms
Blobs processed: 2Consumption vs Premium Plan
| Consumption | Premium (EP) | Dedicated (App Service) | |
|---|---|---|---|
| Cold start | Higher (up to 10s) | Lower (always warm) | None (always on) |
| Scaling | Auto, up to 200 instances | Auto, up to 100 | Manual scale |
| Timeout | 5 min (default), 10 min (max) | 30 min (default), unlimited (Linux) | Unlimited |
| VNET integration | No | Yes | Yes |
| Always ready | No | Yes (configurable min) | Yes |
| Pricing | Pay per execution | Pay per GB-second + base | Pay per VM size |
Durable Functions
Durable Functions extend Azure Functions with stateful orchestration.
# durable_demo.py
# Simulate Durable Functions chaining pattern
import time
import json
from datetime import datetime
class DurableOrchestrator:
def __init__(self):
self.history = []
def call_activity(self, name, input_data):
"""Simulate calling an activity function."""
start = time.time()
time.sleep(0.3) # Simulate work
result = f"{name}({input_data}) → processed at {datetime.now().strftime('%H:%M:%S')}"
self.history.append({"activity": name, "input": input_data, "result": result, "duration": round((time.time()-start)*1000)})
return result
def orchestrate(self):
"""Simulate a Durable Functions orchestration."""
print("🏁 Orchestration started\n")
# Step 1: Validate order
order = {"order_id": "ORD-2026-001", "amount": 150.00, "customer": "dodatech"}
step1 = self.call_activity("ValidateOrder", order)
print(f" Step 1: {step1}")
# Step 2: Process payment
step2 = self.call_activity("ProcessPayment", {"order_id": order["order_id"], "amount": order["amount"]})
print(f" Step 2: {step2}")
# Step 3: Send confirmation (fan-out)
confirmations = ["SendEmail", "SendSMS", "UpdateCRM"]
for activity in confirmations:
result = self.call_activity(activity, {"order_id": order["order_id"]})
print(f" Step 3: {result}")
print(f"\n🏁 Orchestration completed")
return {"status": "completed", "steps": len(self.history), "history": self.history}
orch = DurableOrchestrator()
result = orch.orchestrate()
print(f"\n=== Orchestration Summary ===")
for step in result["history"]:
print(f" {step['activity']:<20} {step['duration']:>4}ms")
print(f"\nTotal steps: {result['steps']}")Expected output:
🏁 Orchestration started
Step 1: ValidateOrder({'order_id': 'ORD-2026-001', ...}) → processed at 10:00:01
Step 2: ProcessPayment({'order_id': 'ORD-2026-001', ...}) → processed at 10:00:01
Step 3: SendEmail({'order_id': 'ORD-2026-001'}) → processed at 10:00:01
Step 3: SendSMS({'order_id': 'ORD-2026-001'}) → processed at 10:00:02
Step 3: UpdateCRM({'order_id': 'ORD-2026-001'}) → processed at 10:00:02
🏁 Orchestration completed
=== Orchestration Summary ===
ValidateOrder 300ms
ProcessPayment 300ms
SendEmail 301ms
SendSMS 300ms
UpdateCRM 301ms
Total steps: 5Common Azure Functions Mistakes
1. Not Handling Cold Starts for Production APIs
Consumption plan cold starts can exceed 10 seconds for C#/.NET. Use Premium plan with “always ready” instances for latency-sensitive HTTP APIs.
2. Writing to Blob Inside Function Instead of Using Output Bindings
Using the SDK to write to Blob is verbose and error-prone. Declare [Blob("output/{name}.txt")] as an output binding — the runtime handles it.
3. Overloading the 5-Minute Timeout
Consumption plan functions timeout after 5 minutes (default). Long-running operations should use Durable Functions or Premium plan with extended timeout.
4. Ignoring Logging and Application Insights
Azure Functions automatically integrates with Application Insights for monitoring. Without it, debugging failures is extremely difficult.
5. Tight Coupling Between Functions
Each function should be independent. Use Queue Storage or Event Grid for loose coupling between functions — function A writes to queue, function B reads from it.
Practice Questions
1. What is the difference between Azure Functions Consumption and Premium plans?
Consumption scales automatically but has cold starts and 5-min timeout. Premium has always-warm instances, VNET integration, unlimited timeout, and predictable pricing with base cost.
2. What are bindings in Azure Functions?
Bindings are declarative connections to Azure services. Input bindings read data into your function; output bindings write data from your function. They eliminate SDK boilerplate.
3. What are Durable Functions used for?
Durable Functions enable stateful orchestration of serverless workflows — chaining activities, fan-out/fan-in, human interaction patterns, and monitoring. They manage state and checkpoints automatically.
4. How do you connect Azure Functions to other Azure services?
Through triggers (start execution: HTTP, Blob, Queue, Timer) and bindings (read/write data: Blob, Cosmos DB, SQL, SendGrid). Also via Event Grid for event routing.
5. Challenge: Design a serverless order processing system using Azure Functions with payment validation, inventory check, shipping, and notification.
Durable Functions orchestration: HTTP trigger to start order → ValidatePayment activity → CheckInventory activity → parallel ReserveInventory + ChargePayment → CreateShipment → SendConfirmation. Use Queue Storage between services for loose coupling.
Mini Project: Multi-Step Azure Function Workflow
# azure_workflow.py
# Simulate a multi-step Azure Functions workflow
import time
import json
class Workflow:
def __init__(self):
self.steps = []
self.context = {}
def add_step(self, name, func, depends_on=None):
self.steps.append({"name": name, "func": func, "depends_on": depends_on or []})
def run(self, initial_input):
results = {}
for step in self.steps:
all_deps_ready = all(dep in results for dep in step["depends_on"])
if not all_deps_ready:
print(f" ⏳ {step['name']}: waiting for dependencies")
continue
print(f" ▶ {step['name']}: executing...")
output = step["func"]({"input": initial_input, "context": results})
results[step["name"]] = output
print(f" ✓ {output}")
return results
wf = Workflow()
wf.add_step("ValidateOrder", lambda ctx: "Order validated")
wf.add_step("ProcessPayment", lambda ctx: f"Payment of ${ctx['input']['amount']} processed", depends_on=["ValidateOrder"])
wf.add_step("UpdateInventory", lambda ctx: "Inventory updated", depends_on=["ProcessPayment"])
wf.add_step("SendNotification", lambda ctx: "Email + SMS sent", depends_on=["UpdateInventory"])
wf.add_step("AuditLog", lambda ctx: "Audit logged", depends_on=["ProcessPayment", "UpdateInventory"])
print("=== Azure Functions Workflow ===\n")
wf.run({"order_id": "ORD-001", "amount": 129.99})
print("\n✓ Workflow complete")Expected output:
=== Azure Functions Workflow ===
▶ ValidateOrder: executing...
✓ Order validated
▶ ProcessPayment: executing...
✓ Payment of $129.99 processed
▶ UpdateInventory: executing...
✓ Inventory updated
▶ SendNotification: executing...
✓ Email + SMS sent
▶ AuditLog: executing...
✓ Audit logged
✓ Workflow completeRelated Concepts
What’s Next
You now understand Azure Functions! Next, compare with AWS Lambda and Google Cloud Run, and explore cloud monitoring for observability.
- Practice daily — Create an HTTP-triggered function in the Azure Portal
- Build a project — Build a serverless image resizer with Blob trigger
- Explore related topics — Check out Azure Logic Apps for no-code workflows
Remember: every expert was once a beginner. Keep coding!
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro