↓ Chuyển đến nội dung chính

API OpenAPI Dev

Lên kế hoạch API trước khi bắt tay xây dựng: quy trình OpenAPI-first

Quy trình lên kế hoạch bằng OpenAPI mà đội tôi áp dụng, để frontend và backend thống nhất hợp đồng API với nhau trước khi sprint bắt đầu, kèm một tệp YAML mẫu.

Lên kế hoạch API trước khi bắt tay xây dựng: quy trình OpenAPI-first

Cách đây không lâu, tôi có viết một bài nói chung chung về phát triển theo hướng OpenAPI-first. Bài này thì cụ thể hơn: đây là quy trình lên kế hoạch mà đội tôi đang chạy thử, kèm theo tệp YAML mẫu.

Chuyện này bắt nguồn từ đâu
#

Mọi chuyện bắt đầu từ một buổi họp nhìn lại (retrospective). Góp ý hôm đó khá thẳng thắn: gần như không ai nắm được khâu lên kế hoạch API đang diễn ra thế nào. Backend xây endpoint theo cách hiểu yêu cầu của riêng mình, frontend chỉ biết phản hồi (response) của API có cấu trúc ra sao khi mọi thứ đã xong xuôi, và những chỗ vênh nhau chỉ lộ ra lúc tích hợp, đúng thời điểm sửa tốn kém nhất. Đề xuất đưa ra là kéo cả hai bên vào ngay từ giai đoạn lên kế hoạch, và lấy bản đặc tả OpenAPI (OpenAPI schema) làm trục chính cho mọi thứ xoay quanh.

Quy trình
#

Mỗi tuần, chúng tôi chọn ra hai đại diện, một người bên backend và một người bên frontend. Nhiệm vụ của họ là nắm rõ yêu cầu của sprint và chỉnh bản đặc tả OpenAPI dùng cho khâu lên kế hoạch sao cho khớp với yêu cầu đó. Hai người cùng rà soát bản đặc tả, sửa những chỗ chưa khớp, và đến cuối buổi thì cả hai bên đều biết chính xác sẽ xây cái gì, trước khi có ai bắt tay vào làm.

Khi các chỉnh sửa đã chốt xong, bản đặc tả được commit vào kho mã (repo), và quy trình CI/CD sẽ sinh ra API giả lập (mock API) từ tệp yaml đó. Hết sprint, chúng tôi tạo một tệp yaml mới cho yêu cầu API của sprint kế tiếp, và vòng lặp cứ thế tiếp tục.

Ghi chú về công cụ
#

  • Muốn xem trực quan tệp yaml thì Swagger Editor là đủ: tải tệp lên là bạn sẽ thấy nó hiển thị thành một tài liệu Swagger đàng hoàng.
  • Bản đặc tả này chỉ phục vụ khâu lên kế hoạch. Khi API thật đã được triển khai, dịch vụ nên tự sinh ra openapi.json của riêng nó; bạn có thể tải tệp đó về rồi dùng lại cho vòng lên kế hoạch tiếp theo.
  • Quy trình CI/CD cần nhận vào một tệp yaml và dựng máy chủ giả lập (mock server) từ đó. Với việc này, openapi-mock trông có vẻ là một lựa chọn đáng tin.

Sao phải mất công như vậy
#

Vì làm vậy thì các cuộc tranh cãi sẽ diễn ra ở nơi rẻ nhất có thể. Khi có một kỹ sư frontend và một kỹ sư backend cùng ngồi viết bản đặc tả, những chỗ hiểu lầm sẽ được phát hiện trước khi kịp biến thành mã. Yêu cầu API chỉ cần thống nhất một lần, thay vì phải thương lượng lại giữa sprint. Nhờ có máy chủ giả lập, frontend bắt tay vào làm được ngay chứ không phải ngồi chờ. Và số chi tiết quan trọng bị bỏ sót cũng ít hơn hẳn, vì muốn lọt được thì chúng phải qua mắt hai người, lại còn phải qua một bản hợp đồng giấy trắng mực đen.

Chẳng có gì mang tính cách mạng ở đây cả. Chỉ là kế hoạch được làm cụ thể đến mức máy có thể dựng lại nó thành bản giả lập cho bạn dùng. Và hóa ra, đó cũng chính là mức cụ thể mà một bản kế hoạch cần có.

Phụ lục A - Ví dụ yaml
#

openapi: 3.1.0
info:
  title: MyApp Sprint 3
  description: Planning schema for sprint 3. Drives the mock server only.
  version: 0.3.0
servers:
  - url: https://api-mock.example.com/v1
tags:
  - name: lists
    description: Saved lists feature (PROJ-123)
security:
  - api_key: []
paths:
  /lists:
    get:
      tags: [lists]
      summary: Return the current user's saved lists
      operationId: getLists
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/List'
  /lists/{listId}:
    get:
      tags: [lists]
      summary: Find a list by ID
      operationId: getListById
      parameters:
        - name: listId
          in: path
          required: true
          schema:
            type: integer
            format: int64
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/List'
        '404':
          description: List not found
components:
  schemas:
    List:
      type: object
      required: [id, title]
      properties:
        id:
          type: integer
          format: int64
          examples: [10]
        title:
          type: string
          examples: [Weekend reading]
        itemCount:
          type: integer
          examples: [7]
  securitySchemes:
    api_key:
      type: apiKey
      name: X-API-Key
      in: header