API First Design: Cách để AI tạo OpenAPI/Swagger từ Business Logic
7/19/2026 · 12p đọc
title: "API First Design: Cách để AI tạo OpenAPI/Swagger từ Business Logic"
series: "AI-Native Solutions Architect: Từ Coder đến Kiến trúc sư AI"
season: "Season 2 — AI-Augmented Architecture & System Design"
order: 16
audience: "Software Engineer hướng tới Solutions Architect"
reading_time: "13 phút"
tags: ["api-first", "openapi", "swagger", "contract-driven-development", "ai-augmented-architecture", "api-design", "backend", "frontend-collaboration"]
API First Design: Cách để AI tạo OpenAPI/Swagger từ Business Logic
Bạn đã từng gặp cảnh này chưa: FE team hỏi "endpoint tạo booking trả về field gì khi lỗi trùng lịch?", bạn mở code BE ra tìm, thấy ba chỗ throw exception khác nhau với ba format response khác nhau, và file Swagger trên Confluence thì ghi ngày cập nhật cuối cùng là ba tháng trước. Đây không phải lỗi cá nhân — đây là hậu quả tất yếu của việc viết code trước, viết tài liệu sau.
Trong mô hình "code-first", spec API là sản phẩm phụ, sinh ra bằng cách quét code hiện có (@nestjs/swagger decorator, JSDoc, hoặc tệ hơn là viết tay). Mỗi lần logic nghiệp vụ đổi — thêm field optional, đổi status code lỗi, thêm rate limit — code chạy trước, decorator cập nhật sau (nếu ai đó nhớ), và tài liệu public-facing cập nhật sau nữa (nếu ai đó rảnh). Chuỗi trễ này tích luỹ thành một khoảng cách vĩnh viễn giữa "API mà FE nghĩ là đang gọi" và "API mà BE thực sự expose".
API-First đảo ngược trật tự: spec OpenAPI được viết và thống nhất TRƯỚC, cả BE lẫn FE code song song dựa trên cùng một hợp đồng, và spec đó — không phải code — là nguồn sự thật (source of truth). Cái mới trong bài này không phải là khái niệm API-First (đã có từ lâu, các công ty như Amazon áp dụng triết lý "API as a product" từ hơn một thập kỷ trước), mà là cách AI hạ thấp chi phí viết spec xuống gần bằng chi phí mô tả nghiệp vụ bằng lời — khiến rào cản lớn nhất của API-First (viết YAML tốn thời gian) gần như biến mất.
Vấn đề
Xét một tình huống cụ thể trong một hệ thống CRM/booking: Product Manager mô tả yêu cầu "khách hàng tạo booking mới, cần chọn dịch vụ, ngày giờ, và số lượng người tham gia". Với quy trình code-first truyền thống, chuyện gì xảy ra tiếp theo thường là:
- BE engineer nhận yêu cầu, tự suy diễn field names (
serviceIdhayservice_id?startTimehaybookingDate+bookingTime?), tự quyết định status code lỗi (409 hay 422 khi trùng lịch?), rồi code thẳng vào controller. - FE engineer, vì cần bắt đầu code UI song song để kịp deadline, tự đoán shape của request/response dựa trên Figma và kinh nghiệm cũ, mock data theo phỏng đoán riêng.
- Đến lúc tích hợp (thường là gần cuối sprint), hai bên phát hiện field không khớp: BE trả
status: "CONFIRMED", FE mong đợibookingStatus: "confirmed". Cả hai phải refactor, một bên chịu thiệt. - Swagger doc được sinh ra bằng decorator sau khi code đã "chốt", nhưng không ai review lại xem decorator có phản ánh đúng validation logic thực tế (ví dụ maxLength của ghi chú, hay rule "participants tối thiểu 1, tối đa theo capacity của dịch vụ") hay không.
Chi phí thực của mô hình này không nằm ở công sức viết tài liệu — nó nằm ở việc hai đội phát triển song song trên hai giả định khác nhau, rồi trả giá bằng thời gian tích hợp và bug production khi giả định sai. Trong một hệ thống nhiều team (BE, FE, mobile, đối tác tích hợp qua public API), chi phí này nhân lên theo số lượng consumer.
API-First không loại bỏ hoàn toàn rủi ro sai lệch — nhưng nó chuyển thời điểm phát hiện sai lệch từ "lúc tích hợp" sang "lúc review spec", khi sửa một dòng YAML rẻ hơn nhiều so với sửa code đã viết ở cả hai phía.
Kỹ thuật cốt lõi
API-First có ba trụ cột kỹ thuật, và AI tác động trực tiếp vào trụ cột tốn thời gian nhất.
Trụ cột 1 — Spec là hợp đồng, không phải tài liệu. OpenAPI spec (định dạng YAML hoặc JSON theo chuẩn OpenAPI Specification, thường gọi tắt là "Swagger spec" theo tên công cụ khởi nguồn) mô tả đầy đủ: đường dẫn, method, request schema, response schema cho từng status code, error format, security scheme. Điểm khác biệt so với code-first: spec này được review và approve TRƯỚC khi dòng code đầu tiên của implementation được viết.
Trụ cột 2 — Codegen hai chiều. Từ một spec đã chốt, có thể sinh: server stub (controller skeleton, DTO/interface types) cho BE, và client SDK (typed fetch functions, TypeScript types) cho FE. Cả hai team code trên cùng một schema, loại bỏ khả năng lệch field name hay kiểu dữ liệu ở tầng biên dịch (compile-time) thay vì phát hiện ở tầng tích hợp (integration-time).
Trụ cột 3 — AI làm cầu nối biz-logic → spec. Đây là phần thực sự thay đổi cuộc chơi so với 5 năm trước. Trước kia, viết OpenAPI spec tay đòi hỏi biết cú pháp YAML của spec (schema, $ref, oneOf, discriminator...) — một rào cản kỹ thuật khiến nhiều team bỏ qua bước "viết spec trước" và quay về code-first cho nhanh. AI (LLM) có thể nhận một mô tả nghiệp vụ bằng ngôn ngữ tự nhiên và sinh ra spec hợp lệ về mặt cú pháp gần như ngay lập tức, hạ chi phí viết spec xuống mức tương đương viết một đoạn mô tả yêu cầu.
Điều quan trọng cần nhấn mạnh: AI sinh spec nhanh, nhưng spec đúng cú pháp không có nghĩa là spec đúng với hệ thống thật. Vai trò của kiến trúc sư không biến mất — nó chuyển từ "gõ YAML" sang "review và đối chiếu spec AI sinh ra với ràng buộc kỹ thuật thật" (data type của DB hiện có, rate limit của gateway, giới hạn của message queue nếu endpoint là async...). Quy trình đề xuất:
flowchart LR
A["Business requirement<br/>(ngôn ngữ tự nhiên)"] --> B["AI sinh draft<br/>OpenAPI YAML"]
B --> C{"Architect review<br/>đối chiếu ràng buộc<br/>hệ thống thật"}
C -- "phát hiện lệch" --> B
C -- "đạt chuẩn" --> D["Spec chốt<br/>(source of truth)"]
D --> E["BE: codegen<br/>controller/DTO stub"]
D --> F["FE: codegen<br/>typed client SDK"]
E --> G["Code song song,<br/>test theo contract"]
F --> G
Vòng lặp "AI sinh draft → architect review → sửa" có thể lặp 2-3 lần cho một endpoint phức tạp, nhưng vẫn nhanh hơn nhiều so với việc kiến trúc sư tự gõ YAML từ đầu.
Thực hành
Bước 1 — Prompt mẫu yêu cầu AI sinh OpenAPI spec từ mô tả nghiệp vụ
Đây là điểm mấu chốt: prompt phải cung cấp đủ ngữ cảnh kỹ thuật (không chỉ mô tả nghiệp vụ suông), nếu không AI sẽ bịa ra các ràng buộc hợp lý-nghe-được nhưng sai với hệ thống thật. Dưới đây là prompt mẫu dùng để sinh spec cho endpoint "tạo booking mới":
Bạn là API architect. Hãy sinh một đoạn OpenAPI 3.1 spec (định dạng YAML)
cho MỘT endpoint duy nhất, dựa trên mô tả nghiệp vụ và ràng buộc kỹ thuật sau.
MÔ TẢ NGHIỆP VỤ:
- Endpoint: khách hàng (đã đăng nhập) tạo một booking mới cho một dịch vụ.
- Input bắt buộc: serviceId (ID dịch vụ), scheduledAt (thời điểm bắt đầu),
participantsCount (số người tham gia).
- Input tuỳ chọn: note (ghi chú của khách hàng, tối đa 500 ký tự).
- Business rule: participantsCount phải >= 1 và <= capacity của dịch vụ
(capacity lấy từ bảng services, không truyền vào request).
- Business rule: không cho phép tạo booking nếu đã có booking khác của
CÙNG service trùng khung giờ (conflict theo scheduledAt + duration
của service) — trả lỗi 409 nếu trùng.
- Business rule: scheduledAt phải là thời điểm trong tương lai (ít nhất
30 phút kể từ thời điểm gọi API) — trả lỗi 422 nếu vi phạm.
- Response thành công: trả về booking vừa tạo, gồm id, status (giá trị
enum: PENDING, CONFIRMED, CANCELLED), serviceId, scheduledAt,
participantsCount, createdAt.
RÀNG BUỘC KỸ THUẬT CỦA HỆ THỐNG (BẮT BUỘC tuân theo, không tự suy diễn khác):
- Auth: Bearer JWT, header Authorization, verify qua middleware hiện có.
- Database: cột participants_count trong MySQL là TINYINT UNSIGNED
(giới hạn cứng 0-255) — schema phải validate maximum tương ứng.
- Rate limit: endpoint POST đang bị giới hạn 20 requests/phút/user qua
API Gateway (Redis-based) — spec cần mô tả trong description và
response 429.
- Định dạng lỗi chuẩn của hệ thống (áp dụng cho MỌI mã lỗi >= 400):
{ "statusCode": number, "message": string, "errorCode": string,
"timestamp": string (ISO 8601) }
- Tên field trong JSON dùng camelCase (khớp entity TypeORM hiện có),
KHÔNG dùng snake_case dù DB column là snake_case.
- Không tự thêm field nào ngoài mô tả nghiệp vụ ở trên. Nếu thấy mô tả
còn thiếu field hoặc rule quan trọng (ví dụ: idempotency key, phân
trang, versioning), liệt kê thành danh sách "CÂU HỎI CẦN LÀM RÕ" ở
cuối câu trả lời thay vì tự đoán.
Xuất kết quả gồm 2 phần: (1) đoạn YAML OpenAPI hoàn chỉnh cho path này
(bao gồm components/schemas dùng $ref), (2) danh sách câu hỏi cần làm rõ (nếu có).
Lưu ý cấu trúc prompt: phần "RÀNG BUỘC KỸ THUẬT" không phải là chi tiết thừa — đây là phần quyết định spec có dùng được hay không. Yêu cầu cuối cùng ("liệt kê câu hỏi cần làm rõ thay vì tự đoán") là kỹ thuật quan trọng để chặn AI "điền vào chỗ trống" bằng phỏng đoán hợp lý nhưng sai.
Bước 2 — Spec YAML kết quả (minh hoạ)
Đây là dạng output kỳ vọng từ prompt trên — dùng làm baseline để review, không phải để copy nguyên xi cho hệ thống khác:
openapi: 3.1.0
info:
title: Booking Service API
version: 1.0.0
paths:
/bookings:
post:
operationId: createBooking
summary: Tạo booking mới cho một dịch vụ
security:
- bearerAuth: []
description: >
Tạo booking cho user đã xác thực. Giới hạn 20 requests/phút/user
(rate limit áp dụng qua API Gateway, trả 429 nếu vượt).
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateBookingRequest'
responses:
'201':
description: Booking được tạo thành công
content:
application/json:
schema:
$ref: '#/components/schemas/BookingResponse'
'401':
description: Thiếu hoặc sai JWT token
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'409':
description: Trùng lịch với booking khác của cùng dịch vụ
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'422':
description: >
Vi phạm business rule (scheduledAt không hợp lệ, hoặc
participantsCount vượt capacity của dịch vụ)
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'429':
description: Vượt rate limit (20 req/phút/user)
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
schemas:
CreateBookingRequest:
type: object
required:
- serviceId
- scheduledAt
- participantsCount
properties:
serviceId:
type: string
format: uuid
description: ID của dịch vụ được booking
scheduledAt:
type: string
format: date-time
description: >
Thời điểm bắt đầu booking (ISO 8601), phải cách thời điểm
gọi API tối thiểu 30 phút
participantsCount:
type: integer
minimum: 1
maximum: 255
description: >
Số người tham gia. Giới hạn cứng 255 do cột DB là
TINYINT UNSIGNED; giới hạn thực tế theo capacity dịch vụ
được validate ở tầng application, không phải ở schema này.
note:
type: string
maxLength: 500
nullable: true
BookingResponse:
type: object
properties:
id:
type: string
format: uuid
status:
type: string
enum: [PENDING, CONFIRMED, CANCELLED]
serviceId:
type: string
format: uuid
scheduledAt:
type: string
format: date-time
participantsCount:
type: integer
createdAt:
type: string
format: date-time
ErrorResponse:
type: object
properties:
statusCode:
type: integer
message:
type: string
errorCode:
type: string
timestamp:
type: string
format: date-time
Chú ý cách spec này thể hiện rõ ràng buộc kỹ thuật thật (comment maximum: 255 gắn với lý do "cột DB là TINYINT UNSIGNED") thay vì chỉ nói chung chung "giới hạn hợp lý". Đây là dấu hiệu cho thấy prompt đã cung cấp đủ ngữ cảnh — nếu spec AI sinh ra không có những chi tiết bám sát hệ thống thật kiểu này, đó là tín hiệu cần bổ sung ngữ cảnh và sinh lại.
Bước 3 — Đưa spec vào quy trình làm việc thực tế
Sau khi spec được chốt (qua review giữa BE lead, FE lead, và kiến trúc sư), quy trình thực thi trong một stack NestJS + Next.js điển hình:
- BE: dùng spec làm nguồn tham chiếu để viết DTO (
class-validatordecorator phải khớpminimum/maximum/maxLengthtrong spec) và controller (@nestjs/swaggerdecorator để spec runtime khớp spec đã chốt — không phải sinh spec runtime rồi coi đó là "chân lý mới"). - FE: dùng codegen tool (ví dụ
openapi-typescripthoặc tương đương) để sinh type definitions từ spec YAML, dùng trực tiếp trongzodschema hoặc type choreact-hook-form. - CI: thêm bước kiểm tra spec runtime (sinh từ decorator khi service chạy) không lệch khỏi spec đã chốt trong repo — phát hiện "code đã âm thầm lệch hợp đồng" trước khi merge, thay vì để FE phát hiện lúc tích hợp.
Cạm bẫy thường gặp
Spec nghe hợp lý nhưng sai với ràng buộc kỹ thuật thật. Đây là rủi ro lớn nhất và là lý do bài này nhấn mạnh "Ràng buộc kỹ thuật" trong prompt. Nếu bạn chỉ đưa AI mô tả nghiệp vụ suông ("tạo booking với serviceId, thời gian, số người"), AI sẽ tự chọn kiểu dữ liệu "hợp lý" — ví dụ participantsCount không giới hạn, hoặc rate limit không được nhắc tới — và spec trông hoàn toàn chuyên nghiệp trong khi âm thầm vi phạm giới hạn TINYINT của cột DB hiện có, hoặc bỏ sót response 429 mà API Gateway thực tế sẽ trả về. Luôn đối chiếu spec với: kiểu dữ liệu cột DB thật, rate limit tầng gateway, format lỗi chuẩn hệ thống, security scheme thật — trước khi coi spec là "chốt".
Đồng bộ hai chiều bị đứt gãy theo thời gian. Spec chốt hôm nay, nhưng nếu team không có cơ chế CI kiểm tra spec runtime (sinh từ decorator) khớp với spec trong repo, sáu tháng sau bạn quay lại đúng vấn đề ban đầu: code lệch tài liệu, chỉ khác là bây giờ "tài liệu" trông chuyên nghiệp hơn (có YAML, có Swagger UI) nên người ta tin tưởng nó hơn — và vì vậy hậu quả khi nó sai còn nặng hơn tài liệu Confluence cũ mà ai cũng biết là "có thể lỗi thời".
Lạm dụng AI generate cho toàn bộ spec lớn cùng lúc. Sinh một file OpenAPI cho 30 endpoint cùng lúc từ một mô tả nghiệp vụ dài dòng khiến việc review trở nên bất khả thi — không ai đọc kỹ hết 2000 dòng YAML để bắt lỗi. Nên sinh và review từng endpoint hoặc từng nhóm endpoint liên quan (ví dụ toàn bộ CRUD của một resource), giữ vòng lặp review đủ nhỏ để thực sự đối chiếu được với ràng buộc thật.
🧭 Góc nhìn Solutions Architect
Spec này có thể tồn tại độc lập với code hiện tại không, hay nó chỉ là "code hiện tại viết lại bằng YAML"? Nếu ba team (BE, FE, đối tác thứ ba) code song song từ spec này, điểm nào dễ gây hiểu lầm nhất — và điểm đó có được ghi rõ trongdescriptionhay bị bỏ ngỏ cho suy diễn? Nếu business rule đổi tuần sau, quy trình cập nhật spec → codegen lại → review diff có tốn ít hơn quy trình sửa code trực tiếp ở cả hai phía không — nếu không, API-First chỉ là thêm một bước thủ tục.
🔗 Bài viết liên quan
- [Bài 15] Event-Driven Design với AI hỗ trợ — khi hợp đồng giữa các service không phải là REST endpoint mà là event schema, nguyên tắc "spec trước, code sau" áp dụng tương tự cho message payload.
- [Bài 20] AI Integration Patterns — những LLM/Agents — khi hệ thống của bạn expose API cho AI agent gọi (không phải người hay FE), OpenAPI spec càng quan trọng vì agent dựa hoàn toàn vào spec để suy luận cách gọi đúng.
Bài trước: Event-Driven Design · Bài tiếp theo: Infrastructure as Code (IaC)