Bindings en Triggers in Azure Functions: Minder Code, Meer Integratie
Hoe declaratieve bindings het verschil maken tussen 50 regels boilerplate en 5 regels werkende code
Jean-Pierre Broeders
Freelance DevOps Engineer
Bindings en Triggers in Azure Functions: Minder Code, Meer Integratie
Een van de grootste voordelen van Azure Functions wordt vaak vergeten: bindings. Waar andere serverless platforms handmatige SDK calls vereisen voor elke integratie, lost Azure Functions dit op met declaratieve configuratie.
Het verschil? In plaats van 30 regels code om een bericht uit een queue te halen, verwerk je het met 3 regels.
Wat Zijn Bindings Eigenlijk?
Bindings zijn input/output declaraties die automatisch data ophalen of wegschrijven. Triggers zijn speciale input bindings die je functie starten.
Een typisch scenario: een HTTP request komt binnen (trigger), haalt user data uit Cosmos DB (input binding), en schrijft een audit log naar Table Storage (output binding). Geen SDK initialisatie, geen connection string management, geen retry logic.
[FunctionName("GetUserProfile")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest req,
[CosmosDB(
databaseName: "Users",
collectionName: "Profiles",
ConnectionStringSetting = "CosmosDB",
Id = "{Query.userId}",
PartitionKey = "{Query.userId}"
)] UserProfile profile,
[Table("AuditLog")] IAsyncCollector<AuditEntry> auditLog)
{
if (profile == null)
return new NotFoundResult();
await auditLog.AddAsync(new AuditEntry
{
Action = "ProfileViewed",
UserId = profile.Id,
Timestamp = DateTime.UtcNow
});
return new OkObjectResult(profile);
}
Geen CosmosClient, geen TableClient, geen handmatige connection pooling. De runtime regelt het.
Waarom Dit Schaalt
Het framework handelt:
- Connection pooling - hergebruik van TCP verbindingen
- Retry logic - automatisch opnieuw proberen bij transient failures
- Secret management - via App Settings of Key Vault referenties
- Dependency injection - bindings werken naast custom DI services
Dit betekent minder kans op lekken (connections, memory), consistente error handling, en snellere development cycles.
Meest Gebruikte Bindings
| Binding Type | Use Case | Direction |
|---|---|---|
| HTTP Trigger | REST API endpoints | In |
| Queue Trigger | Async processing, event-driven workflows | In |
| Timer Trigger | Scheduled jobs (cron-achtig) | In |
| Cosmos DB | Document reads/writes, change feed reacties | In/Out |
| Blob Storage | File uploads, batch processing | In/Out |
| Service Bus | Reliable messaging, pub/sub patterns | In/Out |
| SignalR | Real-time push naar clients | Out |
Binding Expressions: Runtime Parameters
Bindings ondersteunen dynamic values via curly braces:
[CosmosDB(
Id = "{userId}", // van route parameter
PartitionKey = "{region}" // van query string
)]
Dit werkt met:
- HTTP route values (
{id}van/users/{id}) - Query parameters (
{Query.filter}) - Headers (
{Headers.x-correlation-id}) - Trigger payload properties (
{data.orderId}van een queue message)
Voorbeeld: een blob trigger die output schrijft naar een dynamische container:
[FunctionName("ProcessImage")]
public static async Task Run(
[BlobTrigger("uploads/{name}")] Stream image,
[Blob("processed/{name}", FileAccess.Write)] Stream output)
{
// Resize/compress logic
await ResizeImageAsync(image, output);
}
De {name} placeholder wordt automatisch opgevuld vanuit de trigger blob path.
Custom Bindings
Eigen integraties? Bouw een custom binding extension.
Stappen:
- Implementeer
IAsyncCollector<T>voor output ofIConverter<TAttribute, T>voor input - Registreer in
Startup.csviaAddExtension<T>() - Gebruik je attribute in functies
Voorbeeld: een Slack notification binding.
public class SlackAttribute : Attribute
{
public string Channel { get; set; }
}
public class SlackAsyncCollector : IAsyncCollector<string>
{
private readonly string _webhookUrl;
private readonly string _channel;
public SlackAsyncCollector(string webhookUrl, string channel)
{
_webhookUrl = webhookUrl;
_channel = channel;
}
public async Task AddAsync(string message, CancellationToken token = default)
{
var payload = new { channel = _channel, text = message };
await PostToSlackAsync(payload);
}
public Task FlushAsync(CancellationToken token = default) => Task.CompletedTask;
}
Gebruik:
[FunctionName("AlertOnError")]
public static async Task Run(
[QueueTrigger("errors")] ErrorMessage error,
[Slack(Channel = "#alerts")] IAsyncCollector<string> slack)
{
await slack.AddAsync($"⚠️ Error in {error.Service}: {error.Message}");
}
Binding Configuration: Best Practices
1. Gebruik App Settings voor connection strings
Nooit hardcoded. Altijd via ConnectionStringSetting:
[CosmosDB(ConnectionStringSetting = "CosmosDB")]
In local.settings.json:
{
"Values": {
"CosmosDB": "AccountEndpoint=https://..."
}
}
2. Batch processing met IAsyncCollector
In plaats van per-item writes:
// ❌ Langzaam - elke write is een separate call
foreach (var item in items)
{
await tableClient.AddEntityAsync(item);
}
// ✅ Snel - batched door de runtime
[Table("Orders")] IAsyncCollector<Order> output
foreach (var item in items)
{
await output.AddAsync(item);
}
// FlushAsync wordt automatisch aangeroepen
3. Read-only bindings voor queries
Input bindings zijn lazy-loaded. Als de functie niet het object gebruikt, wordt de query niet uitgevoerd.
[CosmosDB(...)] UserProfile profile // alleen geladen als je profile.Name gebruikt
Dit bespaart RU's (Cosmos) of IO operations (Storage).
Output Binding Return Values
Vanaf .NET 5+ isolated worker model: return values als output binding.
[Function("CreateOrder")]
[QueueOutput("order-processing")]
public static OrderMessage Run(
[HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req)
{
var order = ParseOrder(req);
return new OrderMessage { OrderId = order.Id, Items = order.Items };
}
Geen IAsyncCollector nodig voor single-item outputs. Cleaner code, minder verbosity.
Wanneer Geen Bindings Gebruiken
Bindings zijn niet altijd de oplossing:
- Complex queries - Cosmos SQL queries met joins/aggregaties? Gebruik
CosmosClientdirect - Transacties - als atomicity vereist is over meerdere resources
- Custom retry logic - specifieke backoff strategieën die afwijken van defaults
- Performance tuning - fine-grained control over batch sizes, timeouts, connection limits
In die gevallen: inject de SDK client via DI en handel het zelf af.
Monitoring en Troubleshooting
Binding failures verschijnen in Application Insights onder "dependencies".
Veelvoorkomende issues:
- Missing App Setting - functie start niet, "Cannot resolve parameter" error
- Permission errors - Managed Identity heeft geen role assignment (Cosmos, Storage)
- Throttling - te veel concurrent executions, rate limits raken
Check de Live Metrics stream tijdens deployment voor real-time binding performance.
Bindings zijn een van de redenen waarom Azure Functions productiever is dan raw Lambda of Cloud Functions. Minder plumbing, meer business logic. Als je declaratief kan uitdrukken wat je nodig hebt, doe het. De runtime heeft jaren aan productie-hardening meegekregen die je zelf niet wil herbouwen.
