Menu
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Đang tải...

Bài 11 — Viết API Spec với OpenAPI/Swagger

Technical BA Masterclass Bài 11/60

Mở đầu — vì sao bài này quan trọng

Ở Bài 10, chúng ta đã bàn về các nguyên tắc thiết kế REST API: tài nguyên, động từ HTTP, status code, cấu trúc URL. Nhưng có một câu hỏi mà rất nhiều bạn Technical BA gặp phải ngay sau đó: "Em hiểu nguyên tắc rồi, nhưng làm sao để viết ra một bản đặc tả API mà cả đội backend, frontend, tester và đối tác đều đọc được, không cãi nhau, không hiểu sai?"

Câu trả lời chính là bài học hôm nay: OpenAPI Specification (tên cũ là Swagger Specification). Đây là chuẩn công nghiệp toàn cầu để mô tả REST API bằng một file văn bản có cấu trúc (YAML hoặc JSON), máy đọc được mà người cũng đọc được.

Tại sao điều này lại quan trọng đến vậy với một BA? Hãy hình dung dự án tích hợp giữa một ngân hàng và một ví điện tử như MoMo hay ZaloPay. Nếu bạn gửi cho đối tác một file Word dài 40 trang mô tả API, họ sẽ phải đọc thủ công, tự đoán định dạng JSON, tự viết code gọi thử. Sai sót là chuyện đương nhiên. Nhưng nếu bạn gửi một file OpenAPI, đối tác có thể: tự sinh code client, tự dựng mock server để test trước khi bạn code xong, tự sinh tài liệu đẹp như Stripe, và tự kiểm tra request của họ có đúng schema không. Một file thay cho hàng chục cuộc họp làm rõ.

Với BA, OpenAPI không chỉ là "tài liệu kỹ thuật" — nó là hợp đồng (contract) giữa các đội. Và người viết hợp đồng đó, trong nhiều tổ chức, chính là Technical BA.

Khái niệm cốt lõi

OpenAPI là gì, Swagger là gì?

Nhiều bạn bối rối giữa hai tên này. Hãy nhớ đơn giản: OpenAPIchuẩn đặc tả (specification), còn Swaggerbộ công cụ xoay quanh chuẩn đó. Ngày xưa Swagger vừa là chuẩn vừa là tool. Năm 2016, chuẩn được hiến tặng cho Linux Foundation và đổi tên thành OpenAPI, còn cái tên Swagger giờ chỉ còn dùng cho các công cụ như Swagger UI, Swagger Editor, Swagger Codegen. Phiên bản phổ biến hiện nay là OpenAPI 3.0.x và 3.1.x.

Cấu trúc xương sống của một file OpenAPI

Một file OpenAPI có vài khối lớn mà bạn bắt buộc phải nắm:

  • openapi: phiên bản chuẩn đang dùng, ví dụ 3.0.3.
  • info: metadata của API — tên, phiên bản, mô tả, thông tin liên hệ.
  • servers: danh sách base URL (môi trường production, staging, sandbox).
  • paths: trái tim của tài liệu — liệt kê từng endpoint, từng phương thức HTTP, kèm tham số, request body, response.
  • components: kho tái sử dụng — định nghĩa schema dữ liệu, security scheme, response mẫu, để không phải lặp lại.
Một khung tối thiểu trông như sau:

openapi: 3.0.3
info:
  title: Orders API
  version: 1.0.0
  description: API quản lý đơn hàng cho hệ thống bán lẻ
servers:
  - url: https://api.example-bank.vn/v1
    description: Production
  - url: https://sandbox.example-bank.vn/v1
    description: Môi trường thử nghiệm
paths:
  /orders:
    get:
      summary: Lấy danh sách đơn hàng
      responses:
        '200':
          description: Thành công

Schema — nơi BA thể hiện sự sắc bén

Phần components/schemas là nơi bạn mô tả hình dạng dữ liệu: một Order gồm những trường nào, kiểu gì, trường nào bắt buộc, ràng buộc giá trị ra sao. Đây chính là chỗ một BA giỏi tạo ra khác biệt, vì bạn đang dịch nghiệp vụ thành ràng buộc kỹ thuật:

components:
  schemas:
    Order:
      type: object
      required: [orderId, amount, currency, status]
      properties:
        orderId:
          type: string
          example: "ORD-2026-00012345"
        amount:
          type: number
          format: decimal
          minimum: 0
          example: 1500000
        currency:
          type: string
          enum: [VND, USD]
        status:
          type: string
          enum: [PENDING, PAID, CANCELLED]
        createdAt:
          type: string
          format: date-time

Để ý: enum giới hạn giá trị hợp lệ (tránh đối tác gửi currency: "vnd" viết thường), minimum: 0 chặn số tiền âm, required nói rõ trường nào không được thiếu. Mỗi dòng này thay thế cho một câu trong tài liệu nghiệp vụ — và máy có thể kiểm tra tự động.

$ref — nguyên tắc DRY trong đặc tả

Khi Order xuất hiện ở nhiều endpoint, bạn không viết lại nó nhiều lần. Bạn định nghĩa một lần trong components rồi tham chiếu bằng $ref: '#/components/schemas/Order'. Sửa một chỗ, áp dụng mọi nơi. Đây là tư duy "single source of truth" mà mọi tài liệu kỹ thuật tốt đều phải có.

Tình huống thực tế

Tình huống 1: Tích hợp thanh toán giữa Tiki và một cổng thanh toán

Một đội BA tại Tiki cần tích hợp thêm một cổng thanh toán mới để khách hàng quẹt thẻ quốc tế. Bên đối tác (giả định là cổng "VNPay-like") gửi sang một file Word 35 trang. Đội backend Tiki đọc, hiểu nhầm trường txnRef là số nguyên trong khi thực tế nó là chuỗi có cả chữ. Hậu quả: sau hai tuần code, đến lúc test mới phát hiện, phải sửa lại model dữ liệu, trễ tiến độ mười ngày.

Ở dự án tích hợp tiếp theo, BA của Tiki yêu cầu đối tác cung cấp file OpenAPI thay vì Word. Họ nạp file vào Swagger Editor, lập tức thấy txnRef được khai báo type: string, pattern: '^[A-Za-z0-9]{1,34}$'. Không còn chỗ cho hiểu nhầm. Hơn nữa, đội frontend dùng Swagger Codegen sinh sẵn code gọi API, đội QA dùng Prism dựng mock server để test giao diện trước khi backend hoàn thành.

Bài học: Một bản đặc tả mơ hồ bằng văn xuôi luôn để lại khoảng trống cho hiểu nhầm. OpenAPI biến mỗi ràng buộc thành điều khoản máy kiểm tra được, và đó là vũ khí của BA để cắt giảm rủi ro tích hợp.

Tình huống 2: Đối tác Đông Nam Á và bài toán múi giờ, định dạng tiền

Một fintech Việt Nam mở rộng sang thị trường Indonesia, tích hợp với một ngân hàng địa phương. Trong bản OpenAPI đầu tiên, trường amount được khai báo type: number chung chung. Bên Indonesia gửi sang số tiền 15000.50 cho đồng Rupiah — nhưng IDR thực tế không có phần lẻ, còn VND thì đơn vị nhỏ nhất là đồng. Đội tích hợp suýt nữa làm tròn sai, gây lệch sổ.

BA phụ trách sau đó bổ sung mô tả rõ ràng trong schema: ghi chú description: "Số tiền tính theo đơn vị nhỏ nhất của đồng tiền — VND tính theo đồng, IDR tính theo rupiah, không phần lẻ", đồng thời thêm trường currency dạng enum và ví dụ example cho từng đồng tiền. Họ còn dùng oneOf để mô tả hai dạng payload khác nhau cho hai thị trường.

Bài học: OpenAPI không tự hiểu nghiệp vụ thay bạn. Sức mạnh nằm ở chỗ BA biết điền gì vào description, example, enum, format. Những chi tiết tưởng nhỏ như đơn vị tiền tệ, múi giờ (date-time theo IS 8601 có timezone hay không) chính là nơi dự án xuyên biên giới hay vấp ngã.

Tình huống 3: API nội bộ tại một ngân hàng — tài liệu sống

Tại một ngân hàng cổ phần ở TP.HCM, đội core banking có hơn 200 API nội bộ. Trước đây tài liệu nằm rải rác trong Confluence, mỗi lần code đổi mà tài liệu không đổi, dẫn đến "tài liệu chết" — không ai tin nữa. BA đề xuất chuyển sang viết OpenAPI và đặt file .yaml ngay trong repo cùng source code. Mỗi lần merge code, pipeline CI tự render lại Swagger UI và kiểm tra file có hợp lệ không.

Kết quả sau sáu tháng: các đội mới onboard chỉ cần mở một trang Swagger UI là thấy toàn bộ API, thử gọi trực tiếp ngay trên trình duyệt với nút "Try it out". Số câu hỏi kiểu "API này nhận tham số gì?" gửi cho đội backend giảm rõ rệt.

Bài học: OpenAPI phát huy giá trị tối đa khi nó sống cùng code, không phải tài liệu để một góc. (Chủ đề "tài liệu sống cùng code" sẽ được đào sâu ở Bài 55 — Documentation as Code; ở đây bạn chỉ cần nắm rằng OpenAPI là nền tảng để làm được điều đó.)

Hướng dẫn từng bước

Giả sử bạn được giao đặc tả một endpoint tạo đơn hàng. Đây là quy trình tôi khuyên bạn làm theo:

Bước 1 — Dựng khung file. Mở Swagger Editor (editor.swagger.io, miễn phí, chạy ngay trên trình duyệt). Bắt đầu bằng openapi, info, servers. Ghi rõ version API và ít nhất hai môi trường (production + sandbox).

Bước 2 — Định nghĩa schema dữ liệu trước. Đừng vội viết endpoint. Hãy vào components/schemas mô tả các đối tượng nghiệp vụ trước: Order, OrderItem, Customer. Với mỗi trường, xác định: kiểu dữ liệu, bắt buộc hay không, ràng buộc giá trị (enum, minimum, maxLength, pattern), và một example thực tế. Đây là phần BA đóng góp nhiều nhất.

Bước 3 — Viết endpoint trong paths. Khai báo đường dẫn /orders, phương thức post, kèm summarydescription ngắn gọn. Trỏ requestBody tới schema bằng $ref.

Bước 4 — Liệt kê đầy đủ response. Đây là chỗ BA hay quên. Đừng chỉ ghi 200. Hãy nghĩ đủ kịch bản: 201 tạo thành công, 400 dữ liệu sai, 401 chưa xác thực, 409 đơn trùng (idempotency), 422 lỗi nghiệp vụ. Mỗi response trỏ tới một schema lỗi chuẩn.

paths:
  /orders:
    post:
      summary: Tạo đơn hàng mới
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateOrderRequest'
      responses:
        '201':
          description: Tạo đơn thành công
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
        '400':
          description: Dữ liệu đầu vào không hợp lệ
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: Đơn hàng đã tồn tại (trùng idempotency key)

Bước 5 — Khai báo bảo mật. Dùng components/securitySchemes để mô tả cách xác thực (Bearer token, API key, OAuth2). Chi tiết về OAuth2, JWT, API Key sẽ ở Bài 12; ở đây bạn chỉ cần biết cách khai báo nó trong OpenAPI:

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
security:
  - bearerAuth: []

Bước 6 — Validate và preview. Swagger Editor báo lỗi cú pháp ngay khi bạn gõ. Khi file hợp lệ, panel bên phải render thành tài liệu Swagger UI đẹp mắt. Bạn có thể export, gửi đối tác, hoặc dựng mock server.

Bước 7 — Lấy review từ tech. Gửi file cho lead backend và một tester đọc chéo. Họ sẽ chỉ ra những chỗ schema chưa khớp thực tế kỹ thuật. Đây là vòng phản hồi rẻ nhất bạn có thể có — sửa file YAML rẻ hơn sửa code rất nhiều.

Lỗi thường gặp & mẹo

Lỗi 1 — Chỉ mô tả happy path. Nhiều BA mới chỉ viết response 200. Trong thực tế, phần lớn rắc rối tích hợp đến từ các luồng lỗi. Hãy luôn tự hỏi: nếu thiếu trường thì sao? Nếu token hết hạn? Nếu gọi trùng? Mỗi câu hỏi là một response cần khai báo.

Lỗi 2 — Quên exampledescription. File hợp lệ về cú pháp nhưng đối tác vẫn không hiểu, vì mọi trường đều trống mô tả. example cụ thể (như "ORD-2026-00012345") giúp người đọc hình dung ngay, và là dữ liệu sẵn dùng cho mock server.

Lỗi 3 — Lạm dụng type: object không có thuộc tính. Khai báo một object rỗng nghĩa là "nhận bất cứ gì" — vô tình bạn đã bỏ qua việc đặc tả. Luôn liệt kê properties rõ ràng.

Lỗi 4 — Nhầm nullable với không bắt buộc. Trường không nằm trong required nghĩa là có thể vắng mặt. Trường nullable: true nghĩa là có mặt nhưng giá trị có thể là null. Hai khái niệm khác nhau, dễ gây bug.

Mẹo: Đừng viết OpenAPI từ con số 0 bằng tay nếu API đã chạy — nhiều framework (Spring, FastAPI, NestJS) tự sinh được file OpenAPI từ code. Nhưng khi thiết kế API mới, hãy viết spec trước (design-first), vì đó là lúc BA định hình hợp đồng trước khi một dòng code được viết.

Mẹo: Tách file lớn thành nhiều file nhỏ bằng $ref trỏ sang file ngoài khi API phình to. Một file 3000 dòng sẽ không ai muốn đọc.

Bài tập thực hành

  • Mở editor.swagger.io. Tạo một file OpenAPI 3.0.3 cho API quản lý "khóa học" của một nền tảng học trực tuyến. Định nghĩa schema Course gồm: courseId (string), title (string, bắt buộc, tối đa 200 ký tự), price (number, không âm), level (enum: BEGINNER, INTERMEDIATE, ADVANCED), published (boolean).
  • Viết hai endpoint: GET /courses (lấy danh sách, có tham số query level để lọc) và POST /courses (tạo khóa học mới). Khai báo đầy đủ response cho cả happy path lẫn ít nhất hai luồng lỗi (400, 401).
  • Thêm một schema ErrorResponse dùng chung gồm code (string) và message (string), rồi tham chiếu nó ở các response lỗi bằng $ref.
  • Tự đánh giá: với mỗi trường trong schema, bạn đã có exampledescription chưa? Một đồng nghiệp chưa từng nghe về dự án có đọc file và hiểu được không? Nếu chưa, đó chính là khoảng trống cần bổ sung.

Tóm tắt

OpenAPI Specification là chuẩn công nghiệp để mô tả REST API bằng file YAML/JSON mà cả người và máy đều đọc được. Với Technical BA, nó không chỉ là tài liệu mà là hợp đồng giữa các đội, giúp loại bỏ hiểu nhầm, cho phép sinh code, dựng mock server, và render tài liệu tự động.

Xương sống của một file gồm info, servers, paths, và components. Giá trị BA tạo ra nằm ở phần schemas: dịch ràng buộc nghiệp vụ thành enum, required, minimum, pattern, kèm exampledescription rõ ràng. Quy trình tốt là design-first: định nghĩa schema trước, viết đầy đủ cả luồng lỗi, khai báo bảo mật, validate trên Swagger Editor, rồi lấy review từ tech.

Hãy nhớ ba bài học từ thực tế: ràng buộc mơ hồ luôn sinh ra hiểu nhầm; những chi tiết nhỏ như đơn vị tiền và múi giờ là nơi dự án xuyên biên giới hay vấp; và OpenAPI phát huy tối đa khi sống cùng code. Nắm vững công cụ này, bạn đã có trong tay thứ ngôn ngữ chung để nói chuyện với mọi đội kỹ thuật.