4. 输入输出绑定实战

在 Azure Functions 中,触发器负责“什么时候执行”,而输入输出绑定负责“执行时能拿到什么、执行后把结果放到哪里”。绑定分为输入绑定和输出绑定:输入绑定把外部数据以参数形式注入函数,输出绑定把函数返回值或 out 参数写回目标服务。它的核心价值在于把 SDK 客户端创建、连接字符串读取、序列化、重试、关闭连接等胶水代码从业务逻辑中剥离,让函数体只保留“处理数据”这一件事。

绑定通过 function.json(C# 脚本、JavaScript、Python 等)或特性(C# 类库)声明。一个绑定声明通常包含四个关键字段:name 是函数签名中的参数名,type 是绑定类型,direction 是 in/out,connection 指向应用设置中的连接字符串键名。下面这张图展示了同一函数同时使用 Blob 输入、Queue 输出、Cosmos DB 输出和 Event Hubs 触发时的绑定关系与数据流向。

Azure Functions 绑定关系与数据流 Event Hubs 触发器 (Trigger) Blob Storage 输入绑定 (in) Function 业务逻辑 参数注入 / 返回值写出 无 SDK 胶水代码 Queue Storage 输出绑定 (out) Cosmos DB 输出绑定 (out) 事件流 读取内容 写入消息 写入文档 绑定声明关键字段(function.json 示例) name:函数签名中的参数名,如 inputBlob、outputQueue type:绑定类型,如 blob、queue、cosmosDB、eventHubTrigger direction:in(输入)或 out(输出) connection:应用设置中的连接字符串键名,如 AzureWebJobsStorage path / containerName / databaseName / collectionName:目标资源路径

以 Python 为例,一个同时使用 Blob 输入和 Queue 输出的函数,其 function.json 可以这样声明:

{
  "bindings": [
    {
      "name": "myBlob",
      "type": "blobTrigger",
      "direction": "in",
      "path": "samples-workitems/{name}",
      "connection": "AzureWebJobsStorage"
    },
    {
      "name": "outputQueue",
      "type": "queue",
      "direction": "out",
      "queueName": "processed-items",
      "connection": "AzureWebJobsStorage"
    }
  ]
}

对应的 Python 函数体只需要声明参数并返回值:

import logging
import azure.functions as func

def main(myBlob: func.InputStream, outputQueue: func.Out[str]):
    content = myBlob.read().decode("utf-8")
    logging.info(f"Blob 大小: {myBlob.length} 字节")
    outputQueue.set(f"已处理: {myBlob.name}, 长度: {len(content)}")

这里没有 BlobServiceClient、没有 QueueClient、没有连接字符串解析、没有 close() 调用。运行时负责创建客户端、读取 Blob 内容、把字符串写入队列,并在函数结束后释放资源。这就是“减少胶水代码”的直接体现。

Cosmos DB 输出绑定同样简洁。假设要把处理结果写入 orders 数据库的 processed 集合,function.json 中增加:

{
  "name": "outputDocument",
  "type": "cosmosDB",
  "direction": "out",
  "databaseName": "orders",
  "collectionName": "processed",
  "createIfNotExists": true,
  "connectionStringSetting": "CosmosDBConnection"
}

函数中通过 outputDocument.set(json.dumps(result)) 写入文档。注意 createIfNotExists 在开发阶段很方便,但生产环境建议预先创建集合,避免权限或吞吐量配置被意外覆盖。

Event Hubs 作为触发器时,绑定声明需要指定 eventHubName、consumerGroup 和 connection。一个常见配置如下:

{
  "name": "events",
  "type": "eventHubTrigger",
  "direction": "in",
  "eventHubName": "telemetry",
  "consumerGroup": "$Default",
  "connection": "EventHubConnection",
  "cardinality": "many"
}

cardinality 设为 many 时,函数接收的是事件数组,适合批量处理;设为 one 时每次只处理一条。批量处理能显著降低函数调用次数和写入压力,但要注意单次执行时间上限和批大小控制。

连接字符串管理:所有 connection 字段都指向应用设置中的键名,而不是直接写连接字符串。本地开发时放在 local.settings.json 的 Values 节点;部署到 Azure 后放在 Function App 的“配置”中。这样同一份代码可以在不同环境切换目标资源,无需修改绑定声明。

绑定虽然减少了胶水代码,但并非所有场景都适合。当需要复杂查询、跨多个集合事务、精细控制重试策略或使用服务特有高级特性时,仍然应该使用 SDK。判断标准很简单:如果绑定能覆盖 80% 的常规读写,就用绑定;如果业务逻辑需要条件查询、分页、批量操作或自定义序列化,就注入 SDK 客户端。两者可以共存,例如用 Event Hubs 触发器接收事件,用 Cosmos DB SDK 执行复杂查询,再用 Queue 输出绑定发送通知。

最后给出一个可参考的绑定配置检查清单,用于在部署前快速核对:

检查项 说明 常见错误
name 与函数签名一致 参数名必须与 function.json 中 name 完全相同 大小写不一致导致绑定失败
direction 正确 输入用 in,输出用 out 把输出写成 in,运行时无法写入
connection 键存在 应用设置中必须有对应键名 本地能跑,部署后报连接字符串缺失
path / queueName 有效 目标容器、队列或数据库已存在 路径拼写错误或资源未创建
输出类型匹配 func.Out[str] 对应字符串,func.Out[bytes] 对应字节 类型不匹配导致序列化异常

掌握绑定的声明式配置后,函数代码会变得非常薄:输入参数直接可用,输出通过 set 或返回值写出,连接和生命周期交给运行时。这样既降低了出错概率,也让单元测试更容易——只需构造输入参数、调用函数、检查输出对象,无需模拟 SDK 客户端。