Hầu hết các lỗi API không phải là lỗi lập trình. Chúng là lỗi về thỏa thuận. Frontend mong đợi `userId`, backend gửi `user_id`, và không ai nhận ra cho đến khi QA. Phát triển API "spec-first" khắc phục điều này bằng cách đặt bản hợp đồng (contract) là thứ đầu tiên bạn viết, chứ không phải là thứ cuối cùng bạn tài liệu hóa.
Trong hướng dẫn này, bạn sẽ tự tay viết một OpenAPI spec nhỏ, sau đó sử dụng một tệp duy nhất đó để tạo ra các mock, các bài kiểm tra và tài liệu trước khi bất kỳ mã máy chủ nào tồn tại. Cách tiếp cận tương tự này còn có một vài tên gọi khác: phát triển theo định nghĩa (spec-driven development), thiết kế trước (design-first), hoặc hợp đồng trước (contract-first). Tất cả đều hướng đến cùng một ý tưởng. Đồng ý về giao diện trước, sau đó xây dựng theo nó.
Phát triển API "spec-first" là gì
Phát triển API "spec-first" có nghĩa là bạn tạo ra một bản hợp đồng có thể đọc được bằng máy, thường là tài liệu OpenAPI, trước khi bạn triển khai endpoint. Bản hợp đồng đó mô tả mọi đường dẫn (path), tham số (parameter), thân yêu cầu (request body), hình dạng phản hồi (response shape) và mã trạng thái (status code). Một khi nó tồn tại, nó trở thành nguồn sự thật duy nhất cho tất cả những ai làm việc với API.
Spec không phải là tài liệu theo sau code. Nó dẫn đầu. Các đội frontend xây dựng dựa trên một mock được tạo ra từ nó. QA viết các bài kiểm tra dựa trên nó. Backend triển khai để đáp ứng nó. Khi cả ba bên đồng ý với cùng một tệp, việc tích hợp sẽ không còn là trò đoán mò nữa.
Điều này đảo ngược thứ tự thông thường. Trong phát triển "code-first", bạn viết các bộ xử lý (handler) và sau đó mô tả chúng, thường là thủ công, thường bị lỗi thời trong một sprint. Trong quy trình làm việc "design-first", mô tả được đưa ra trước và mã phải tuân theo nó. Thay đổi đơn giản đó chuyển hầu hết các vấn đề tích hợp về giai đoạn đầu của dự án, nơi chúng có thể được khắc phục với chi phí thấp.
Vòng đời "spec-first" so với "code-first"
Hai phương pháp tiếp cận tạo ra cùng một các endpoint. Chúng khác nhau ở thời điểm các vấn đề phát sinh và ai có thể bắt đầu làm việc song song.

Cột bên phải là lợi ích đạt được. Khi hợp đồng tồn tại trước, ba đội không còn cản trở lẫn nhau mà bắt đầu xây dựng dựa trên một định nghĩa chung.
Ví dụ OpenAPI thực tế
Hãy thiết kế một endpoint nhỏ `/users` từng bước một. Chúng ta sẽ xây dựng nó thành các phần để mỗi phần được rõ ràng, sau đó lắp ráp thành một tệp hoàn chỉnh.
Bắt đầu với phần tiêu đề tài liệu. Điều này khai báo phiên bản OpenAPI và các siêu dữ liệu cơ bản.
openapi: 3.0.3
info:
title: Users API
version: 1.0.0
servers:
- url: https://api.example.com/v1
Tiếp theo, định nghĩa lược đồ (schema) `User` trong `components`. Định nghĩa nó một lần cho phép bạn tham chiếu nó ở mọi nơi, do đó hình dạng vẫn nhất quán trên các yêu cầu và phản hồi.
components:
schemas:
User:
type: object
required: [id, email, createdAt]
properties:
id:
type: string
format: uuid
email:
type: string
format: email
name:
type: string
createdAt:
type: string
format: date-time
Bây giờ hãy thêm đường dẫn `GET /users`. Nó liệt kê người dùng và hỗ trợ tham số truy vấn `limit`. Lưu ý cách phản hồi sử dụng lại lược đồ `User` bằng `$ref` thay vì định nghĩa lại.
paths:
/users:
get:
summary: List users
operationId: listUsers
parameters:
- name: limit
in: query
schema:
type: integer
default: 20
maximum: 100
responses:
"200":
description: A list of users
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/User"
Thêm thao tác `POST /users` để tạo người dùng. Thân yêu cầu (request body) định nghĩa những gì client phải gửi, và phản hồi `201` trả về bản ghi đã tạo.
post:
summary: Create a user
operationId: createUser
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [email]
properties:
email:
type: string
format: email
name:
type: string
responses:
"201":
description: User created
content:
application/json:
schema:
$ref: "#/components/schemas/User"
"400":
description: Invalid request body
Đó là một bản hợp đồng hoàn chỉnh, hợp lệ. Nó đặt tên cho mọi trường, đánh dấu `email` là bắt buộc, giới hạn `limit` ở 100, và khai báo cả phản hồi thành công và lỗi. Chưa ai viết một dòng mã máy chủ nào, nhưng thỏa thuận đã được khóa lại.
Tạo mock, test và tài liệu từ spec
Lý do để viết spec trước tiên là để tận dụng đòn bẩy. Một tệp duy nhất điều khiển ba thành phần mà các đội thường xây dựng riêng biệt.
Mocks. Một máy chủ mock đọc spec của bạn và trả về các phản hồi ví dụ khớp với mọi lược đồ. Các gợi ý `format: uuid` và `format: email` cho phép công cụ tạo dữ liệu mẫu thực tế. Frontend của bạn gọi `GET /users` và nhận được một mảng người dùng được định dạng tốt ngay từ ngày đầu tiên, rất lâu trước khi handler thực sự tồn tại. Khi spec thay đổi, mock cũng thay đổi theo.
Tests. Vì spec khai báo các trường bắt buộc, kiểu dữ liệu và mã trạng thái, nó còn đóng vai trò như một nguồn đáng tin cậy để kiểm thử (test oracle). Các bài kiểm tra hợp đồng (contract tests) khẳng định rằng phản hồi thực sự cho `POST /users` trả về `201` với thân phản hồi khớp với lược đồ `User`, và `400` khi `email` bị thiếu. Bạn không tự mình nghĩ ra các khẳng định. Bạn đang kiểm tra xem việc triển khai có khớp với những gì bạn đã đồng ý hay không.
Docs. Tài liệu tham chiếu API được tạo trực tiếp từ spec. Mọi endpoint, tham số và ví dụ bạn thấy trong tài liệu đều đến từ cùng một tệp YAML. Không có bản sao thứ hai để duy trì đồng bộ, vì vậy tài liệu không thể lệch khỏi bản hợp đồng.
Điều này cũng là lý do tại sao phương pháp "spec-first" kết hợp tốt với quy trình làm việc API git-native: spec là một tệp văn bản thuần túy, vì vậy mọi thay đổi đối với bản hợp đồng đều hiển thị dưới dạng một bản khác biệt (diff) có thể xem xét trong một pull request. Người đánh giá có thể thấy rằng ai đó đã đổi tên `email` hoặc loại bỏ một trường bắt buộc trước khi nó được triển khai.
Thực hiện điều này trong Apidog
Apidog hỗ trợ điều này từ đầu đến cuối thông qua Chế độ Spec-First. Thay vì coi tệp OpenAPI là một bản xuất (export), nó coi tệp là dự án. Bạn chỉnh sửa YAML trực tiếp và phần còn lại của không gian làm việc sẽ tự động cập nhật theo.

Bạn viết hoặc dán spec `/users` vào trình chỉnh sửa. Apidog phân tích nó và ngay lập tức dựng một máy chủ mock cho cả hai thao tác, để frontend của bạn có thể bắt đầu gọi chúng. Tài liệu được tạo sẽ cập nhật khi bạn gõ. Khi bạn sẵn sàng xác minh việc triển khai, bạn chạy các thao tác của spec dưới dạng các trường hợp kiểm thử (test case) đối với backend thực của bạn và xác nhận các phản hồi khớp với lược đồ.
Phần giúp duy trì tính trung thực của phương pháp này là đồng bộ hóa Git hai chiều. Spec của bạn nằm trong một kho lưu trữ (repository), và các thay đổi chảy theo cả hai hướng. Chỉnh sửa YAML trong trình chỉnh sửa của bạn và push, Apidog sẽ nhận nó. Chỉnh sửa trong Apidog, và thay đổi sẽ trở thành một commit mà đội của bạn có thể xem xét. Bản hợp đồng không bao giờ tồn tại ở hai nơi cùng một lúc. Nếu bạn muốn so sánh sâu hơn về vị trí của phương pháp này so với cách tiếp cận thiết kế thuần túy, hãy xem spec-first vs design-first trong Apidog.
Danh sách kiểm tra "spec-first"
Sử dụng danh sách này trước khi bạn tuyên bố một spec sẵn sàng để xây dựng:
- Spec hợp lệ theo lược đồ OpenAPI mà không có lỗi.
- Mọi endpoint đều khai báo phản hồi thành công và ít nhất một phản hồi lỗi.
- Các hình dạng dùng chung nằm trong `components/schemas` và được tham chiếu bằng `$ref`, không phải sao chép.
- Các trường bắt buộc được đánh dấu `required`, và các định dạng (`uuid`, `email`, `date-time`) được đặt ở những nơi phù hợp.
- Tệp spec được commit vào hệ thống kiểm soát phiên bản và được xem xét trong các pull request.
- Một máy chủ mock chạy từ spec, và frontend có thể truy cập nó.
- Các bài kiểm tra hợp đồng kiểm tra các phản hồi thực tế so với các lược đồ đã khai báo.
- Các tài liệu đã xuất bản được tạo từ cùng một tệp, không có bản sao được duy trì thủ công.
Nếu mọi ô đều được chọn, các đội của bạn có thể xây dựng song song dựa trên một thỏa thuận thay vì ba lần đoán mò.
Câu hỏi thường gặp
Phát triển API "spec-first" có giống với "design-first" không?
Đa số là có. "Design-first" và "contract-first" mô tả cùng một nguyên tắc: viết giao diện trước khi triển khai. "Spec-first" là tên gọi trực tiếp nhất vì tệp OpenAPI spec là thành phần cụ thể mà bạn bắt đầu. Trong thực tế, các thuật ngữ này được sử dụng thay thế cho nhau.
Tôi có phải tự tay viết YAML không?
Không. Bạn có thể tạo spec trong một trình chỉnh sửa trực quan và để nó tạo ra YAML, hoặc viết YAML trực tiếp nếu bạn thích. Vấn đề không phải là định dạng bạn gõ, mà là bản hợp đồng tồn tại và được thống nhất trước khi viết code. Nhiều đội kết hợp cả hai, phác thảo trực quan và tinh chỉnh trong YAML trong quá trình xem xét.
Làm thế nào để tôi ngăn chặn sự khác biệt giữa spec và code?
Đặt spec làm nguồn sự thật và thực thi nó. Giữ spec trong Git để các thay đổi được xem xét dưới dạng các bản khác biệt (diff). Chạy các bài kiểm tra hợp đồng trong CI để bản dựng thất bại khi một phản hồi không còn khớp với lược đồ. Với đồng bộ hóa hai chiều, các chỉnh sửa ở một trong hai nơi đều được đối chiếu, loại bỏ nguyên nhân phổ biến nhất gây ra sự khác biệt.
Phát triển API "spec-first" là một thay đổi nhỏ về thứ tự nhưng mang lại sự thay đổi lớn về kết quả. Viết bản hợp đồng, đồng ý về nó, sau đó xây dựng theo nó. Nếu bạn muốn thử toàn bộ quy trình, hãy mở Chế độ Spec-First trong Apidog và trỏ nó đến kho lưu trữ của bạn.
