Mục tiêu: Master Azure Functions triggers/bindings, Durable Functions, và serverless patterns cho .NET.
1. Triggers & Bindings
Trigger: khởi động function (HTTP, Timer, Queue, Blob, Event Grid...)
Input Binding: đọc data vào function
Output Binding: ghi data ra từ function
┌──────────┐ Trigger ┌────────────┐ Output ┌──────────┐
│ Event │─────────────→│ Function │────────────→│ Target │
│ Source │ │ Code │ │ (DB, │
└──────────┘ Input │ │ │ Queue, │
│ Cosmos │─────────────→│ │ │ Blob) │
└──────────┘ └────────────┘ └──────────┘
Isolated Worker Model (.NET 8/9)
csharp// Program.cs - .NET 8+, Isolated Worker (recommended)
var host = new HostBuilder()
.ConfigureFunctionsWebApplication()
.ConfigureServices(services =>
{
services.AddDbContext<AppDbContext>();
services.AddSingleton<IEmailService, SendGridEmailService>();
})
.Build();
host.Run();
// HTTP Trigger
public class ProductFunctions(AppDbContext db, ILogger<ProductFunctions> logger)
{
[Function("GetProducts")]
public async Task<HttpResponseData> GetProducts(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "products")] HttpRequestData req)
{
var products = await db.Products.AsNoTracking().ToListAsync();
var response = req.CreateResponse(HttpStatusCode.OK);
await response.WriteAsJsonAsync(products);
return response;
}
[Function("GetProduct")]
public async Task<HttpResponseData> GetProduct(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "products/{id}")] HttpRequestData req,
Guid id)
{
var product = await db.Products.FindAsync(id);
if (product is null)
return req.CreateResponse(HttpStatusCode.NotFound);
var response = req.CreateResponse(HttpStatusCode.OK);
await response.WriteAsJsonAsync(product);
return response;
}
}
// Service Bus Trigger
public class OrderProcessor(ILogger<OrderProcessor> logger)
{
[Function("ProcessOrder")]
public async Task ProcessOrder(
[ServiceBusTrigger("order-queue", Connection = "ServiceBusConnection")] OrderMessage message,
FunctionContext context)
{
logger.LogInformation("Processing order {OrderId}", message.OrderId);
// Process order...
}
}
// Timer Trigger
public class ScheduledJobs(ILogger<ScheduledJobs> logger)
{
[Function("DailyCleanup")]
public async Task DailyCleanup(
[TimerTrigger("0 0 2 * * *")] TimerInfo timer) // Daily at 2 AM UTC
{
logger.LogInformation("Running daily cleanup at {Time}", DateTime.UtcNow);
// Cleanup expired records, temp files, etc.
}
}
// Blob Trigger
public class FileProcessor
{
[Function("ProcessUpload")]
[BlobOutput("processed/{name}", Connection = "StorageConnection")]
public async Task<byte[]> ProcessUpload(
[BlobTrigger("uploads/{name}", Connection = "StorageConnection")] byte[] content,
string name,
FunctionContext context)
{
var logger = context.GetLogger<FileProcessor>();
logger.LogInformation("Processing file: {FileName}, size: {Size}", name, content.Length);
// Process file: resize image, convert format, etc.
var processed = await ProcessImage(content);
return processed; // Auto-saved to "processed" container
}
}
2. Durable Functions
Orchestration Patterns
csharp// Pattern 1: Function Chaining (sequential)
[Function("OrderOrchestration")]
public static async Task<OrderResult> RunOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
var order = context.GetInput<OrderRequest>()!;
// Each step runs in separate function, state persisted
var validationResult = await context.CallActivityAsync<ValidationResult>(
"ValidateOrder", order);
if (!validationResult.IsValid)
return OrderResult.Failed(validationResult.Errors);
var inventoryResult = await context.CallActivityAsync<bool>(
"ReserveInventory", order);
var paymentResult = await context.CallActivityAsync<PaymentResult>(
"ProcessPayment", order);
var confirmation = await context.CallActivityAsync<string>(
"SendConfirmation", order);
return OrderResult.Success(confirmation);
}
// Activity functions
[Function("ValidateOrder")]
public static ValidationResult ValidateOrder(
[ActivityTrigger] OrderRequest order) => /* validate */;
[Function("ReserveInventory")]
public static async Task<bool> ReserveInventory(
[ActivityTrigger] OrderRequest order) => /* reserve stock */;
// Starter
[Function("StartOrderProcessing")]
public static async Task<HttpResponseData> Start(
[HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req,
[DurableClient] DurableTaskClient client)
{
var order = await req.ReadFromJsonAsync<OrderRequest>();
string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(
"OrderOrchestration", order);
return await client.CreateCheckStatusResponseAsync(req, instanceId);
}
csharp// Pattern 2: Fan-out/Fan-in (parallel processing)
[Function("BatchProcessOrchestrator")]
public static async Task<int> BatchProcess(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
var files = await context.CallActivityAsync<List<string>>("GetPendingFiles");
// Fan-out: process ALL files in parallel
var tasks = files.Select(file =>
context.CallActivityAsync<ProcessResult>("ProcessFile", file));
var results = await Task.WhenAll(tasks);
// Fan-in: aggregate results
return results.Count(r => r.IsSuccess);
}
// Pattern 3: Human Interaction (approval workflow)
[Function("ApprovalOrchestrator")]
public static async Task<string> ApprovalWorkflow(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
var request = context.GetInput<ApprovalRequest>()!;
// Send notification
await context.CallActivityAsync("SendApprovalEmail", request);
// Wait for approval event (with timeout)
using var cts = new CancellationTokenSource();
var approvalTask = context.WaitForExternalEvent<bool>("ApprovalReceived");
var timeoutTask = context.CreateTimer(
context.CurrentUtcDateTime.AddDays(3), cts.Token);
var winner = await Task.WhenAny(approvalTask, timeoutTask);
if (winner == approvalTask)
{
cts.Cancel();
return approvalTask.Result ? "Approved" : "Rejected";
}
return "Timed out";
}
// External event sender (called from web app)
[Function("SubmitApproval")]
public static async Task SubmitApproval(
[HttpTrigger] HttpRequestData req,
[DurableClient] DurableTaskClient client)
{
var body = await req.ReadFromJsonAsync<ApprovalResponse>();
await client.RaiseEventAsync(body!.InstanceId, "ApprovalReceived", body.Approved);
}
3. Cold Start & Performance
Cold Start Times (approximate):
┌──────────────┬────────────┬───────────┐
│ Plan │ .NET 8 │ .NET AOT │
├──────────────┼────────────┼───────────┤
│ Consumption │ 1-3 sec │ <500ms │
│ Premium │ <1 sec │ <200ms │
│ Dedicated │ Pre-warmed │ Pre-warmed│
└──────────────┴────────────┴───────────┘
Strategies to reduce cold start:
1. Premium Plan: pre-warmed instances
2. Native AOT: minimal startup time
3. Keep-alive pings (Timer trigger every 5 min)
4. Minimize dependencies in function project
5. Use ReadyToRun compilation
Production Best Practices
csharp// 1. host.json tuning
{
"version": "2.0",
"extensions": {
"serviceBus": {
"maxConcurrentCalls": 16,
"prefetchCount": 100,
"maxAutoLockRenewalDuration": "00:30:00"
},
"queues": {
"maxPollingInterval": "00:00:02",
"batchSize": 32
}
},
"functionTimeout": "00:10:00",
"logging": {
"logLevel": {
"default": "Warning",
"Function": "Information"
}
}
}
// 2. Retry policy
[Function("ProcessOrder")]
[FixedDelayRetry(5, "00:00:30")]
public async Task ProcessOrder(
[ServiceBusTrigger("orders")] OrderMessage message) { }
// 3. Monitor with Application Insights
builder.Services.AddApplicationInsightsTelemetryWorkerService();
4. Ví Dụ: File Processing Pipeline
Blob Upload → Blob Trigger → Validate → Process → Store Result → Notify
[Upload] [Validate] [Process] [Store] [Notify]
│ │ │ │ │
│ Blob │ Queue │ Queue │ Queue │
│ Trigger │ Trigger │ Trigger │ Trigger │
▼ ▼ ▼ ▼ ▼
Upload to Validate file Convert/resize Save to DB Send email
"uploads" format & size image + metadata notification
container → "validated" → "processed"
queue queue
Tổng Kết Session 21
| Concept | Key Takeaway |
|---|---|
| Isolated Worker | .NET 8/9 recommended model, full DI support |
| Triggers | HTTP, Timer, Service Bus, Blob, Cosmos, Event Grid |
| Durable Functions | Stateful orchestrations, fan-out/fan-in, human interaction |
| Cold Start | Premium plan hoặc AOT cho latency-sensitive |
| Retry | Built-in [FixedDelayRetry], [ExponentialBackoffRetry] |
Bài tập: Tạo Durable Function orchestration: upload file (HTTP) → validate → process → store result → send notification. Deploy lên Azure Functions Premium Plan.