Tìm hiểu cách tạo OpenAPI từ hành vi bằng Claude Code, so sánh với implementation API và tạo các ví dụ xác thực client và server đơn giản.

Một hợp đồng OpenAPI là mô tả chung về API của bạn: những endpoint nào tồn tại, bạn gửi gì, nhận lại gì, và lỗi trông như thế nào. Đó là thỏa thuận giữa server và bất cứ thứ gì gọi nó (ứng dụng web, ứng dụng di động, hoặc dịch vụ khác).
Vấn đề là drift. API đang chạy thay đổi, nhưng spec thì không. Hoặc spec bị “dọn dẹp” để trông đẹp hơn thực tế, trong khi implementation vẫn trả các trường lạ, thiếu mã trạng thái, hoặc dạng lỗi không nhất quán. Dần dần, mọi người mất niềm tin vào file OpenAPI, và nó trở thành một tài liệu bị bỏ qua.
Drift thường đến từ áp lực thông thường: một sửa nhanh được phát hành mà không cập nhật spec, một trường optional mới được thêm “tạm thời”, phân trang tiến hóa, hoặc các đội cập nhật các “nguồn sự thật” khác nhau (mã backend, bộ sưu tập Postman, và file OpenAPI).
Giữ nó trung thực nghĩa là spec khớp với hành vi thực tế. Nếu API đôi khi trả 409 cho xung đột, điều đó phải có trong hợp đồng. Nếu một trường có thể null, hãy ghi rõ. Nếu cần auth, đừng để mơ hồ.
Một workflow tốt cho bạn kết quả:
Điểm cuối cùng quan trọng vì một hợp đồng chỉ có ích khi nó được thực thi. Một spec trung thực cộng với các kiểm tra lặp lại biến “tài liệu API” thành thứ các đội có thể tin tưởng.
Nếu bạn bắt đầu bằng cách đọc mã hoặc sao chép routes, OpenAPI của bạn sẽ mô tả những gì tồn tại hôm nay, kể cả các tật mà bạn có thể không muốn hứa. Thay vào đó, mô tả API nên làm gì cho một caller, rồi dùng spec để kiểm chứng implementation có khớp.
Trước khi viết YAML hay JSON, thu thập một bộ sự kiện nhỏ cho mỗi endpoint:
Rồi viết hành vi dưới dạng ví dụ. Ví dụ buộc bạn phải cụ thể và dễ hơn để soạn hợp đồng nhất quán.
Với API Tasks, một ví dụ đường vui vẻ có thể là: “Tạo một task với title và nhận về id, title, status, và createdAt.” Thêm các lỗi phổ biến: “Missing title trả 400 với { \"error\": \"title is required\" }” và “Không có auth trả 401.” Nếu bạn đã biết các trường hợp biên, hãy đưa vào: liệu tiêu đề trùng có được phép không, và chuyện gì xảy ra khi id task không tồn tại.
Ghi các quy tắc bằng câu đơn giản không phụ thuộc vào chi tiết mã:
title là bắt buộc và 1-120 ký tự.”limit được đặt (max 200).”dueDate là date-time ISO 8601.”Cuối cùng, quyết định phạm vi v1. Nếu không chắc, giữ v1 nhỏ và rõ ràng (create, read, list, update status). Lưu tìm kiếm, cập nhật hàng loạt, và bộ lọc phức tạp cho sau để hợp đồng dễ tin cậy.
Trước khi bạn yêu cầu Claude Code viết spec, hãy ghi ghi chú hành vi trong một định dạng nhỏ, có thể lặp lại. Mục tiêu là làm cho khó vô tình “điền vào chỗ trống” bằng đoán mò.
Một mẫu tốt đủ ngắn để bạn thực sự dùng, nhưng đủ nhất quán để hai người sẽ mô tả cùng một endpoint tương tự. Giữ tập trung vào API làm gì, không phải cách nó được implement.
Dùng một block cho mỗi endpoint:
METHOD + PATH:
Purpose (1 sentence):
Auth:
Request:
- Query:
- Headers:
- Body example (JSON):
Responses:
- 200 OK example (JSON):
- 4xx example (status + JSON):
Edge cases:
Data types (human terms):
Viết ít nhất một request cụ thể và hai response. Bao gồm mã trạng thái và body JSON thực tế với tên trường thật. Nếu một trường là optional, cho ví dụ một lần nó bị thiếu.
Ghi rõ các trường hợp biên. Những chỗ này thường là nơi spec âm thầm trở nên sai sau vì mọi người giả sử khác nhau: kết quả rỗng, ID không hợp lệ (400 vs 404), trùng lặp (409 vs hành vi idempotent), lỗi validate, và giới hạn phân trang.
Cũng ghi kiểu dữ liệu bằng từ ngữ bình thường trước khi nghĩ về schema: chuỗi vs số, định dạng date-time, boolean, và enum (liệt kê giá trị được phép). Điều này ngăn một schema “đẹp” mà không khớp payload thực.
Claude Code hoạt động tốt nhất khi bạn đối xử với nó như một thư ký cẩn thận. Cho nó ghi chú hành vi và các quy tắc chặt chẽ về cách OpenAPI nên được tạo. Nếu bạn chỉ nói “viết spec OpenAPI,” thường sẽ có đoán mò, tên không nhất quán, và thiếu các trường hợp lỗi.
Dán ghi chú hành vi của bạn trước, rồi thêm khối chỉ dẫn chặt. Một prompt thực tế trông như sau:
You are generating an OpenAPI 3.1 YAML spec.
Source of truth: the behavior notes below. Do not invent endpoints or fields.
If anything is unclear, list it under ASSUMPTIONS and leave TODO markers in the spec.
Requirements:
- Include: info, servers (placeholder), tags, paths, components/schemas, components/securitySchemes.
- For each operation: operationId, tags, summary, description, parameters, requestBody (when needed), responses.
- Model errors consistently with a reusable Error schema and reference it in 4xx/5xx responses.
- Keep naming consistent: PascalCase schema names, lowerCamelCase fields, stable operationId pattern.
Behavior notes:
[PASTE YOUR NOTES HERE]
Output only the OpenAPI YAML, then a short ASSUMPTIONS list.
Sau khi nhận được bản nháp, quét ASSUMPTIONS trước. Đó là nơi mà tính trung thực được giành hoặc mất. Phê duyệt những gì chính xác, sửa những gì sai, và chạy lại với ghi chú đã cập nhật.
Để giữ tên nhất quán, nêu quy ước ngay từ đầu và tuân theo nó. Ví dụ: một pattern operationId ổn định, tên tag chỉ danh từ, tên schema số ít, một schema Error chia sẻ, và một tên auth được dùng mọi nơi.
Nếu bạn làm việc trong môi trường vibe-coding như Koder.ai, việc lưu YAML như một file thực sớm và lặp lại theo từng diff nhỏ sẽ giúp. Bạn có thể thấy thay đổi nào đến từ quyết định hành vi đã được phê duyệt và thay đổi nào model đoán mò.
Trước khi so sánh bất cứ gì với production, đảm bảo file OpenAPI nhất quán nội bộ. Đây là nơi nhanh nhất để bắt suy nghĩ ước muốn và diễn đạt mơ hồ.
Đọc từng endpoint như một developer client. Tập trung vào những gì caller phải gửi và những gì họ có thể tin là sẽ nhận.
Một lượt rà soát thực tế:
Các phản hồi lỗi cần chăm chút thêm. Chọn một dạng chung và tái sử dụng nó khắp nơi. Một số đội giữ rất đơn giản ({ error: string }), số khác dùng object ({ error: { code, message, details } }). Cả hai đều có thể hoạt động, nhưng đừng trộn lẫn chúng giữa các endpoint và ví dụ. Nếu bạn trộn, code client sẽ tích lũy các ngoại lệ.
Một kịch bản kiểm tra nhanh giúp ích. Nếu POST /tasks yêu cầu title, schema nên đánh dấu nó là required, response lỗi nên cho thấy body lỗi bạn thực sự trả, và operation nên nêu rõ có cần auth hay không.
Khi spec đọc như hành vi bạn mong muốn, đối xử API đang chạy là sự thật về trải nghiệm client hôm nay. Mục tiêu không phải là “thắng” giữa spec và code. Mục tiêu là bộc lộ khác biệt sớm và quyết định rõ ràng cho từng trường hợp.
Cho lần kiểm tra đầu, sample request/response thực tế thường là cách đơn giản nhất. Logs và test tự động cũng được nếu đáng tin.
Chú ý các mismatch phổ biến: endpoint có ở nơi này nhưng không ở nơi kia, khác biệt tên trường hoặc hình dạng, khác mã trạng thái (200 vs 201, 400 vs 422), hành vi undocumented (phân trang, sắp xếp, lọc), và khác auth (spec nói public, code yêu cầu token).
Ví dụ: spec nói POST /tasks trả 201 với {id,title}. Bạn gọi API thực và nhận 200 cộng với {id,title,createdAt}. Đó không phải “gần đủ” nếu bạn sinh SDK từ spec.
Trước khi chỉnh sửa gì, quyết định cách giải quyết xung đột:
Giữ mỗi thay đổi nhỏ và dễ review: một endpoint, một response, một chỉnh schema. Dễ review thì dễ retest.
Khi bạn có spec đáng tin, biến nó thành các ví dụ xác thực nhỏ. Đây là thứ ngăn drift quay lại.
Trên server, xác thực nghĩa là fail nhanh khi request không khớp hợp đồng, và trả lỗi rõ ràng. Điều đó bảo vệ dữ liệu và làm lỗi dễ tìm.
Một cách đơn giản biểu diễn ví dụ xác thực server là viết các case gồm ba phần: input, expected output, và expected error (mã lỗi hoặc mẫu thông báo, không phải văn bản chính xác).
Ví dụ (hợp đồng nói title bắt buộc và 1 đến 120 ký tự):
{
"name": "Create task without title returns 400",
"request": {"method": "POST", "path": "/tasks", "body": {"title": ""}},
"expect": {"status": 400, "body": {"error": {"code": "VALIDATION_ERROR"}}}
}
Trên client, xác thực là phát hiện drift trước khi người dùng thấy. Nếu server bắt đầu trả dạng khác, hoặc một trường required biến mất, test nên báo lỗi.
Giữ kiểm tra client tập trung vào những gì bạn thực sự phụ thuộc, như “một task có id, title, status.” Tránh assert mọi trường optional hay thứ tự chính xác. Bạn muốn fail trên thay đổi phá vỡ, không phải trên bổ sung vô hại.
Một vài hướng dẫn để test dễ đọc:
Nếu bạn dùng Koder.ai, bạn có thể sinh và giữ các case này cạnh file OpenAPI, rồi cập nhật chúng cùng lúc trong review khi hành vi thay đổi.
Giả sử một API nhỏ với ba endpoint: POST /tasks tạo task, GET /tasks liệt kê, và GET /tasks/{id} trả một task.
Bắt đầu bằng cách viết vài ví dụ cụ thể cho một endpoint, như thể bạn đang giải thích cho tester.
Với POST /tasks, hành vi mong muốn có thể là:
{ \"title\": \"Buy milk\" } và nhận 201 với một object task mới, gồm id, title, và done:false.{} và nhận 400 với lỗi như { \"error\": \"title is required\" }.{ \"title\": \"x\" } (quá ngắn) và nhận 422 với { \"error\": \"title must be at least 3 characters\" }.Khi Claude Code sinh OpenAPI, đoạn cho endpoint này nên nắm schema, mã trạng thái, và ví dụ thực tế:
paths:
/tasks:
post:
summary: Create a task
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateTaskRequest'
examples:
ok:
value: { \"title\": \"Buy milk\" }
responses:
'201':
description: Created
content:
application/json:
schema:
$ref: '#/components/schemas/Task'
examples:
created:
value: { \"id\": \"t_123\", \"title\": \"Buy milk\", \"done\": false }
'400':
description: Bad Request
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
examples:
missingTitle:
value: { \"error\": \"title is required\" }
'422':
description: Unprocessable Entity
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
examples:
tooShort:
value: { \"error\": \"title must be at least 3 characters\" }
Một mismatch phổ biến rất tinh tế: API đang chạy trả 200 thay vì 201, hoặc trả { \"taskId\": 123 } thay vì { \"id\": \"t_123\" }. Đó là loại “gần giống” khiến SDK sinh tự động bị vỡ.
Sửa bằng cách chọn một nguồn sự thật. Nếu hành vi mong muốn đúng, thay implementation trả 201 và hình dạng Task đã thống nhất. Nếu hành vi production đã được dựa vào, cập nhật spec (và ghi chú hành vi) để khớp thực tế, rồi thêm các xác thực và lỗi thiếu để client không ngạc nhiên.
Hợp đồng trở nên mất trung thực khi nó ngừng mô tả quy tắc và bắt đầu mô tả bất cứ gì API trả vào một ngày đẹp trời. Một bài kiểm tra đơn giản: một implementation mới có thể vượt qua spec này mà không sao chép các tật hiện tại chứ?
Một bẫy là overfitting. Bạn chụp một response và biến nó thành luật. Ví dụ: API hiện trả dueDate: null cho mọi task, nên spec nói trường luôn nullable. Nhưng quy tắc thực sự có thể là “bắt buộc khi status là scheduled.” Hợp đồng nên diễn đạt quy tắc, không chỉ dataset hiện tại.
Lỗi là nơi tính trung thực thường gãy. Dễ dàng spec chỉ phản ánh response thành công vì chúng trông sạch. Nhưng client cần cơ bản: 401 khi token thiếu, 403 khi bị cấm, 404 cho ID không biết, và một lỗi validate nhất quán (400 hoặc 422).
Các pattern khác gây rắc rối:
taskId ở route này nhưng id ở route khác, hoặc priority là string ở chỗ này và number ở chỗ khác).string, mọi thứ thành optional).Một hợp đồng tốt có thể viết test fail từ nó. Nếu bạn không thể viết một test thất bại từ spec, nó chưa trung thực.
Trước khi đưa file OpenAPI cho đội khác (hoặc dán vào docs), làm một lượt nhanh xem “ai đó có thể dùng cái này mà không phải đọc suy nghĩ của bạn không?”
Bắt đầu với ví dụ. Một spec có thể hợp lệ nhưng vẫn vô dụng nếu mọi request và response đều trừu tượng. Với mỗi operation, bao gồm ít nhất một ví dụ request thực tế và một ví dụ response thành công. Với lỗi, một ví dụ cho mỗi lỗi phổ biến (auth, validate) thường là đủ.
Rồi kiểm tra tính nhất quán. Nếu một endpoint trả { \"error\": \"...\" } và endpoint khác trả { \"message\": \"...\" }, client sẽ có logic phân nhánh khắp nơi. Chọn một dạng lỗi duy nhất và tái sử dụng, kèm theo mã trạng thái dự đoán.
Một checklist ngắn:
Một mẹo thực tế: chọn một endpoint, giả vờ bạn chưa từng thấy API, và trả lời “Tôi gửi gì, nhận lại gì, và gì là lỗi?” Nếu OpenAPI không trả lời rõ, nó chưa sẵn sàng.
Workflow này có lợi khi chạy thường xuyên, không chỉ lúc phát hành. Chọn một quy tắc đơn giản và tuân thủ: chạy mỗi khi endpoint thay đổi, và chạy lại trước khi xuất bản spec cập nhật.
Giữ quyền sở hữu đơn giản. Người thay đổi endpoint cập nhật ghi chú hành vi và draft spec. Người thứ hai review diff “spec vs implementation” như review code. QA hoặc support thường là reviewer tốt vì họ phát hiện các phản hồi mơ hồ và trường hợp biên nhanh.
Xử lý chỉnh sửa hợp đồng như chỉnh sửa code. Nếu bạn dùng bộ dựng chat như Koder.ai, chụp snapshot trước các sửa rủi ro và rollback khi cần giữ iteration an toàn. Koder.ai cũng hỗ trợ xuất mã nguồn, giúp giữ spec và implementation cạnh nhau trong repo.
Một routine thường hoạt động mà không làm chậm đội:
Hành động tiếp theo: chọn một endpoint đã tồn tại. Viết 5–10 dòng ghi chú hành vi (inputs, outputs, lỗi), sinh draft OpenAPI từ những ghi chú đó, validate nó, rồi so sánh với implementation đang chạy. Sửa một mismatch, test lại, và lặp. Sau một endpoint, thói quen thường dần bền vững.
OpenAPI drift là khi API mà bạn chạy thực tế không còn khớp với file OpenAPI mà mọi người chia sẻ. Spec có thể thiếu các trường mới, mã trạng thái, hoặc quy tắc xác thực, hoặc nó mô tả hành vi “lý tưởng” mà server không tuân theo.
Nó quan trọng vì các client (ứng dụng, dịch vụ khác, SDK sinh tự động, test) đưa ra quyết định dựa trên hợp đồng, không phải trên những gì server của bạn “thường” làm.
Sự cố client trở nên ngẫu nhiên và khó gỡ lỗi: app di động mong đợi 201 nhưng nhận 200, một SDK không thể deserialize vì một trường bị đổi tên, hoặc xử lý lỗi bị vỡ vì dạng lỗi khác nhau.
Ngay cả khi không có crash, các đội mất niềm tin và ngừng dùng spec — điều đó loại bỏ hệ thống cảnh báo sớm của bạn.
Bởi vì code phản ánh hành vi hiện tại, kể cả những thói quen không mong muốn mà bạn có thể không muốn cam kết lâu dài.
Một mặc định tốt hơn là: viết hành vi dự định trước (inputs, outputs, lỗi), rồi kiểm tra implementation có khớp hay không. Điều này cho bạn một hợp đồng có thể thực thi, thay vì một ảnh chụp nhanh routes của hôm nay.
Với mỗi endpoint, ghi lại:
Nếu bạn có thể viết một request cụ thể và hai response, thường là đủ để soạn spec trung thực.
Chọn một dạng thân lỗi và tái sử dụng nó khắp nơi.
Một mặc định đơn giản là:
{ "error": "message" }, hoặc{ "error": { "code": "...", "message": "...", "details": ... } }Rồi giữ nhất quán trên các endpoint và ví dụ. Tính nhất quán quan trọng hơn độ tinh vi vì client sẽ hard-code theo dạng này.
Cho Claude Code ghi chú hành vi và các quy tắc chặt chẽ, và bảo nó không thêm trường. Một bộ hướng dẫn thực tế:
TODO in the spec and list it under ASSUMPTIONS.”Error) and reference them.”Sau khi sinh, xem trước. Đó là nơi mà tính trung thực bị phá vỡ nếu bạn chấp nhận đoán mò.
Validate spec trước khi so sánh với production:
201)Điều này bắt được các file OpenAPI “mong muốn” trước khi bạn nhìn vào hành vi thực tế.
Đối với mỗi khác biệt, quyết định theo từng trường hợp:
Giữ các thay đổi nhỏ (một endpoint hoặc một response) để dễ retest.
Server-side validation nên từ chối yêu cầu không đúng hợp đồng và trả lỗi rõ ràng (status + code/shape).
Client-side validation nhằm phát hiện drift sớm bằng cách chỉ assert những gì bạn thực sự dựa vào:
Tránh assert mọi trường optional để các test chỉ fail khi có thay đổi phá vỡ, không phải khi có bổ sung vô hại.
Một quy trình thực tế:
Nếu bạn dùng Koder.ai, bạn có thể giữ OpenAPI cạnh mã, chụp snapshot trước sửa rủi ro, và rollback nếu thay đổi lộn xộn.