当事件驱动架构从几个消息主题扩展到数百个生产者、消费者和事件类型时,问题往往不再是“消息能不能发出去”,而是团队能否回答三个问题:事件发到哪里、通过什么基础设施传输、消息究竟长什么样。
Ian Cooper 将这三个问题概括为消息系统的 ABC:Address、Binding、Contract。结合 AsyncAPI、CloudEvents、Schema Registry 和 CI/CD,可以把原本依赖文档、工单和口头约定的异步接口,转化为可发现、可验证、可自动配置的工程资产。
ABC:不要把主题名当成完整的 API
同步 API 通常由 URL、HTTP 方法和请求响应模型共同定义。异步 API 也需要类似的边界,只是组成方式不同。
Address:消息在哪里
Address 是生产者发布消息、消费者订阅消息的位置,例如 Kafka topic、RabbitMQ exchange/routing key,或者云消息服务中的队列和主题。
一个可管理的地址应该具备稳定的命名规则,例如:
orders.created.v1
orders.cancelled.v1
payments.authorised.v1
地址不应该携带经常变化的部署信息。把集群名、团队内部项目代号或临时环境名称直接编码进业务 topic,会让迁移和跨环境部署变得困难。
Binding:消息如何传输
Binding 描述逻辑 API 与具体消息基础设施之间的连接关系,包括协议、broker、topic 配置、分区、副本、认证和交付语义等。
这一区分很重要:OrderCreated 是业务事件,而 Kafka 是当前承载它的技术。业务契约可以保持稳定,但不同环境可能使用不同的 broker 地址、认证方式和容量设置。
Contract:消息是什么意思
Contract 定义消息的字段、类型、必填项和语义。它不仅是一份 JSON 示例,还应说明:
- 事件类型和版本;
- 事件唯一标识;
- 事件来源及发生时间;
- 业务数据的结构;
- 字段能否为空;
- 兼容性与演进规则。
CloudEvents 可以统一事件元数据,JSON Schema 或 Avro 等格式则负责约束业务负载。两者结合后,消费者不必为每个团队重新猜测 eventId、timestamp 和 source 分别叫什么。
用 AsyncAPI 把三个部分放进同一份定义
下面是一份可以直接保存为 asyncapi.yaml 的最小示例。它假设系统使用 Kafka,并采用 CloudEvents 风格的 JSON 事件信封;实际项目中需要替换 broker 地址、分区数以及业务字段。
asyncapi: 2.6.0
info:
title: Order Events API
version: 1.0.0
description: Events emitted by the order service.
servers:
production:
url: kafka.example.internal:9092
protocol: kafka
description: Production Kafka cluster
channels:
orders.created.v1:
description: Published after an order has been accepted.
bindings:
kafka:
topic: orders.created.v1
partitions: 12
replicas: 3
bindingVersion: 0.5.0
subscribe:
operationId: consumeOrderCreated
message:
$ref: '#/components/messages/OrderCreated'
components:
messages:
OrderCreated:
name: OrderCreated
title: Order created event
contentType: application/json
payload:
type: object
additionalProperties: false
required:
- specversion
- id
- source
- type
- time
- data
properties:
specversion:
type: string
const: '1.0'
id:
type: string
description: Globally unique event identifier
source:
type: string
format: uri-reference
type:
type: string
const: com.example.orders.created.v1
time:
type: string
format: date-time
data:
type: object
additionalProperties: false
required:
- orderId
- currency
- total
properties:
orderId:
type: string
currency:
type: string
pattern: '^[A-Z]{3}$'
total:
type: number
minimum: 0
这份文件同时表达了 ABC:
orders.created.v1是 Address;servers和bindings.kafka是 Binding;OrderCreated的 payload 是 Contract。
可以使用 AsyncAPI CLI 在本地验证文件:
npx --yes @asyncapi/cli@latest validate asyncapi.yaml
这里仍需注意一个容易混淆的方向问题:AsyncAPI 中的 publish 和 subscribe 通常从应用视角解释。编写规范前,团队应统一“谁在发布、谁在订阅”的叙述视角,否则生成文档后仍可能产生误解。
让发现、治理和配置进入同一条流水线
把 YAML 提交到仓库只是起点。规模化治理的关键是让规范成为交付入口,而不是上线后补写的文档。
一个实用的仓库可以这样组织:
order-events/
├── asyncapi.yaml
├── examples/
│ └── order-created.json
├── policies/
│ └── ownership.yaml
└── .github/
└── workflows/
└── validate.yaml
对应的 GitHub Actions 工作流如下:
name: Validate asynchronous API
on:
pull_request:
paths:
- 'asyncapi.yaml'
- 'examples/**'
push:
branches: [main]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Validate AsyncAPI document
run: npx --yes @asyncapi/cli@latest validate asyncapi.yaml
在实际平台中,这条流水线还可以按顺序增加以下关卡:
- 语法验证:AsyncAPI 和 Schema 是否有效;
- 组织策略检查:topic 是否符合命名规则,事件是否声明 owner、数据等级和保留期限;
- 兼容性检查:新 schema 是否与已注册版本向后兼容;
- 评审与审批:高风险变更由平台团队或领域负责人批准;
- 基础设施计划:生成 topic、权限、配额和监控规则的变更计划;
- 发布与注册:创建或更新基础设施,并把规范发布到可搜索的 API 目录。
Schema Registry 的具体接口取决于使用的产品,但原则是一致的:不要只检查 schema 是否“合法”,还要检查它是否会破坏现有消费者。例如删除必填字段、改变字段类型,或者重新解释已有枚举值,都可能让下游在运行时失败。
自动配置不等于让所有 YAML 直接上线
从 AsyncAPI 自动创建 topic 和访问策略很有吸引力,但规范描述和基础设施配置并不完全等价。分区数、保留时间、压缩策略和区域复制通常取决于流量、恢复目标以及法规要求,而不只是消息结构。
更稳妥的方式是生成一个可审查的配置计划。例如,平台可以要求服务同时提交一份资源声明:
apiVersion: messaging.example.com/v1
kind: EventStream
metadata:
name: orders-created-v1
spec:
address: orders.created.v1
owner: team-orders
classification: internal
retentionHours: 168
partitions: 12
compatibility: backward
producers:
- order-service
consumers:
- fulfilment-service
- analytics-pipeline
这是一个示意性的内部资源格式,不是通用标准。平台流水线可以将它转换成 Terraform、Kubernetes 自定义资源或云厂商 API 调用,同时生成权限和监控配置。重要的是,生产环境执行前应展示 diff,并为删除 topic、缩短保留期等破坏性操作设置人工审批。
契约演进比格式选择更难
即使使用了 AsyncAPI 和 Schema Registry,事件演进仍需要明确策略。
通常可以安全进行的变更包括:
- 增加消费者可以忽略的可选字段;
- 扩展文档和字段描述;
- 修正不影响机器解析的示例。
需要谨慎处理的变更包括:
- 删除字段或把可选字段改成必填;
- 修改字段类型、单位或业务含义;
- 改变事件触发时机;
- 在现有枚举中加入旧消费者无法处理的新值;
- 把一条事件拆成多条,或改变顺序保证。
当语义发生根本变化时,创建新版本地址并进行一段时间的双写,通常比偷偷修改旧事件更安全。双写也有成本:生产者逻辑更复杂、重复数据增加,而且两个版本可能出现不一致,因此必须设置迁移期限和下线负责人。
落地时可以检查什么
异步 API 平台不应只追求“有多少份规范”,还应检查规范是否真正参与生产流程。可以从下面的清单开始:
- 每个事件是否有明确 owner 和可联系的支持团队;
- Address 是否稳定、可预测,并避免包含环境细节;
- Binding 是否与业务契约解耦;
- Contract 是否有机器可验证的 schema 和示例;
- CloudEvents 元数据是否在团队之间保持一致;
- CI 是否阻止不兼容变更;
- topic、ACL、保留策略和监控是否通过可审计流程配置;
- API 目录是否支持按领域、事件类型和 owner 搜索;
- 是否记录敏感数据分类、删除要求和跨区域限制;
- 是否为旧版本设置迁移计划和明确的下线日期。
ABC 模型的价值,在于把异步消息从“某个 broker 上的一串 topic”提升为正式 API。AsyncAPI 提供描述载体,CloudEvents统一事件元数据,Schema Registry守住兼容性,而 CI/CD 与基础设施流水线则让治理规则真正执行。规模越大,越不能依赖每个团队自觉遵守约定;规则只有进入自动化交付链路,才会成为系统能力。