跳过正文
  1. 文章/

先把 API 设计好再动手:OpenAPI 优先的工作流

· loading · loading ·
仁才德
作者
仁才德
居住在韩国首尔的领导者和软件工程师

前不久我写过一篇 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