前不久我写过一篇 OpenAPI 优先开发的泛泛而谈,这篇是具体版:我们团队正在试行的计划工作流,外加配套的 YAML 示例。
事情的起因#
起因是一次回顾会。反馈说得很不客气:API 的规划几乎没有任何可见性。后端按自己对需求的理解去实现端点,前端事后才知道响应长什么样,分歧全都在集成阶段——也就是代价最高的时刻——才暴露出来。于是有了这个提案:把两边都拉进计划阶段,让 OpenAPI schema 成为一切的锚点。
流程#
每周选两名代表,后端一名、前端一名。他们的任务是吃透本次冲刺的需求,并让计划用的 OpenAPI schema 与之对齐。两人一起评审 schema,把对不上的地方改掉。等这一步做完,在动手之前,双方就已经确切知道要造的是什么了。
修订定稿后,把 schema 提交到仓库,CI/CD 会根据这份 yaml 生成模拟 API。冲刺结束时,再切一份覆盖下个冲刺 API 需求的新 yaml,循环往复。
工具备忘#
- 可视化 yaml 用 Swagger Editor 就够了:把文件传上去,就能看到渲染好的 Swagger 文档。
- 这份 schema 纯粹是计划产物。真实 API 部署之后,应该由各服务自己生成 openapi.json,下载下来再喂给下一轮计划。
- CI/CD 流水线需要能接收 yaml 并据此拉起一个模拟服务器。openapi-mock 看起来是个靠谱的选项。
为什么值得费这个劲#
因为争论被挪到了代价最低的地方。写 schema 的时候前后端工程师都在场,误解还没变成代码就被抓住了。API 需求一次谈妥,不用在冲刺中途反复扯皮;有了模拟服务器,前端立刻就能开工,不用干等;关键细节被漏掉的机会也小得多——它们得同时躲过两双眼睛和一份白纸黑字的契约才行。
这里没有任何革命性的东西。只是把计划做得足够具体,具体到机器能把它当成模拟服务还给你——事实证明,一份计划需要的具体程度,恰好就是这个程度。
附录A - yaml示例#
openapi: 3.1.0
info:
title: PKY Sprint 3
description: |-
PKY Sprint 3 Development Schema
termsOfService:
contact:
email: jared@lynskey.co.nz
license:
name: None
url:
version: 1.0.11
externalDocs:
description:
url:
servers:
- url: https://api-mock.com/api/v3
tags:
- name: curation
description: PKY-1081 Curated Lists Feature
externalDocs:
description: Jira Epic
url: https://pickydev.atlassian.net/browse/PKY-1081
paths:
/curations:
get:
tags:
- curation
summary: Return the curations created by users
description: Returns a map of status codes to quantities
operationId: getCurations
responses:
'200':
description: successful operation
content:
application/json:
schema:
type: object
additionalProperties:
type: integer
format: int32
security:
- api_key: []
/curations/{curationId}:
get:
tags:
- curation
summary: Find purchase order by ID
description: For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions.
operationId: getOrderById
parameters:
- name: orderId
in: path
description: ID of order that needs to be fetched
required: true
schema:
type: integer
format: int64
responses:
'200':
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
application/xml:
schema:
$ref: '#/components/schemas/Order'
'400':
description: Invalid ID supplied
'404':
description: Order not found
delete:
tags:
- curation
summary: Delete purchase order by ID
description: For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors
operationId: deleteOrder
parameters:
- name: orderId
in: path
description: ID of the order that needs to be deleted
required: true
schema:
type: integer
format: int64
responses:
'400':
description: Invalid ID supplied
'404':
description: Order not found
components:
schemas:
Curation:
type: object
properties:
id:
type: integer
format: int64
examples: [10]
curationId:
type: integer
format: int64
examples: [198772]
quantity:
type: integer
format: int32
examples: [7]
status:
type: string
description: Order Status
examples: [approved]
enum:
- placed
- approved
- delivered
complete:
type: boolean
xml:
name: order
ApiResponse:
type: object
properties:
code:
type: integer
format: int32
type:
type: string
message:
type: string
xml:
name: '##default'
requestBodies:
Curation:
description: Curation object that needs to be added
content:
application/json:
schema:
$ref:
application/xml:
schema:
$ref:
securitySchemes:
petstore_auth:
type: oauth2
flows:
implicit:
authorizationUrl:
scopes:
write:curations: modify curations in your account
read:curations: read your curations
api_key:
type: apiKey
name: api_key
in: header
