Trong các hệ sinh thái nền tảng số B2B, cổng thanh toán Open Banking và kiến trúc Microservices quy mô doanh nghiệp năm 2026, giao diện lập trình ứng dụng (RESTful API) không đơn thuần là một tầng vận chuyển JSON qua HTTP, mà là một hợp đồng kỹ thuật có giá trị ràng buộc dài hạn (Long-Lived Technical Contract) giữa hàng trăm ứng dụng khách (Mobile Apps, Web SPA, đối tác tích hợp bên thứ ba) và lõi dịch vụ phía sau. Khi đội ngũ kỹ thuật xem nhẹ tiêu chuẩn thiết kế RESTful API chuẩn Enterprise và versioning, hệ thống sẽ nhanh chóng rơi vào hỗn loạn: mọi phản hồi lỗi đều trả về HTTP 200 OK kèm trường {"success": false} làm mù hoàn toàn hệ thống giám sát Prometheus/Envoy, truy vấn phân trang OFFSET 500000 đánh sập CPU của cụm cơ sở dữ liệu PostgreSQL, và một thay đổi nhỏ đổi tên trường JSON ngay lập tức làm tê liệt ứng dụng di động của hàng trăm nghìn người dùng chưa kịp cập nhật phiên bản.
Khác với giao tiếp nội bộ Đông-Tây (East-West Internal Traffic) vốn ưu tiên hiệu năng nhị phân khi xây dựng High-Performance gRPC Microservices với Go, Protobuf v3 và mTLS, các API hướng ngoại (North-South External & Partner APIs) đòi hỏi khả năng tương thích rộng rãi, ngữ nghĩa chuẩn hóa tuyệt đối và cơ chế tiến hóa phiên bản an toàn. Bài viết chuyên sâu này thiết lập bộ khung kiến trúc RESTful API chuẩn Enterprise năm 2026 dựa trên các đặc tả chính thức mới nhất của IETF và OpenAPI Initiative: chuẩn hóa ngữ nghĩa HTTP theo RFC 9110, định danh tài nguyên thời gian UUIDv7 (RFC 9562), phân trang con trỏ Keyset Cursor Pagination, cấu trúc báo lỗi chuẩn RFC 9457 (application/problem+json), quản trị vòng đời phiên bản & ngừng hỗ trợ với RFC 8594 (Sunset), điều tiết tải chuẩn IETF RateLimit và kiểm định hợp đồng OpenAPI 3.1.1 Contract-First tự động trong CI/CD.
- 1. Kiến trúc RESTful API chuẩn Enterprise: Từ Richardson Maturity Model đến Contract-First 2026
- 1.1. Quy hoạch Resource-Oriented URI, danh từ số nhiều và định danh thời gian UUIDv7 (RFC 9562)
- 1.2. Ngữ nghĩa phương thức HTTP chuẩn IETF RFC 9110 và cơ chế Idempotency-Key cho giao dịch đột biến
- 2. Chuẩn hóa Envelope phản hồi, Keyset Cursor Pagination và lọc dữ liệu quy mô lớn
- 2.1. Tại sao Offset Pagination (LIMIT/OFFSET) gây sập Database ở chiều sâu hàng triệu bản ghi?
- 2.2. Thiết kế Opaque Base64URL Cursor, Sparse Fieldsets và Filtering đa chiều
- 3. Chuẩn hóa HTTP Status Codes và cấu trúc báo lỗi RFC 9457 (application/problem+json)
- 3.1. Phân ranh giới chính xác giữa nhóm mã trạng thái 2xx, 4xx và 5xx trong hệ thống phân tán
- 3.2. Áp dụng chuẩn IETF RFC 9457 Problem Details thay thế các định dạng lỗi tự phát
- 4. Chiến lược Quản lý Phiên bản API (API Versioning) và Vòng đời Deprecation chuẩn RFC 8594
- 4.1. So sánh kiến trúc URI Path Versioning, Header Versioning (Media Type) và Query Parameter
- 4.2. Quản trị Breaking Changes và tự động hóa cảnh báo ngừng hỗ trợ với HTTP Deprecation & Sunset (RFC 8594)
- 4.3. Giám sát lưu lượng phiên bản cũ (Legacy Version Telemetry) trước thời điểm Sunset
- 5. Bảo vệ năng lực xử lý với Rate Limiting chuẩn IETF và Đặc tả OpenAPI 3.1.1 Contract-First
- 5.1. Thiết kế Rate Limiting đa tầng (Token Bucket / Sliding Window) và bộ Header RateLimit-*
- 5.2. Triển khai Contract-First với OpenAPI 3.1.1, FastAPI 0.115+ và kiểm định Breaking Change trên CI/CD
- 6. Checklist Kiểm Toán Thiết Kế RESTful API Doanh Nghiệp & Tài Liệu Tham Khảo Chuẩn Kỹ Thuật
- 6.1. Bảng đối chiếu 10 tiêu chí nghiệm thu RESTful API trước khi đưa lên Production
- 6.2. Tài liệu tham khảo & Nguồn chuẩn kỹ thuật (Primary Technical References)
1. Kiến trúc RESTful API chuẩn Enterprise: Từ Richardson Maturity Model đến Contract-First 2026
Theo mô hình trưởng thành Richardson Maturity Model (RMM), rất nhiều hệ thống tự nhận là RESTful nhưng thực chất vẫn dậm chân ở Level 0 (The Swamp of POX / RPC-over-HTTP): sử dụng duy nhất phương thức POST tới các đường dẫn chứa động từ mệnh lệnh như /api/createNewOrder, /api/getCustomerById hay /api/cancelSubscription. Kiến trúc RESTful chuẩn doanh nghiệp đòi hỏi tối thiểu đạt RMM Level 2 (quy hoạch URI theo danh từ tài nguyên kết hợp đúng ngữ nghĩa phương thức và mã trạng thái HTTP chuẩn RFC 9110) kèm theo các liên kết điều hướng chọn lọc của Level 3 (HATEOAS — Hypermedia Controls) cho luồng chuyển đổi trạng thái và vòng đời phiên bản.
flowchart TD
Client["Client / Partner SDK (HTTP/2 & HTTP/3)"] -->|"GET/POST /v1/orders + Idempotency-Key + X-API-Version"| APIGW["API Gateway (TLS 1.3, AuthN/AuthZ, IETF RateLimit)"]
APIGW --> VersionRouter{"Version & Deprecation Router"}
VersionRouter -->|"Phiên bản sắp ngừng hỗ trợ (v1 cũ)"| InjectSunset["Gắn Header Deprecation + Sunset (RFC 8594) + Link rel=sunset"]
VersionRouter -->|"Phiên bản hiện hành (v1/v2)"| IdemEngine{"Kiểm tra Idempotency-Key (POST/PATCH)"}
InjectSunset --> IdemEngine
IdemEngine -->|"Trùng khóa & Khớp SHA-256"| ReplayCache["Trả ngay kết quả đã lưu từ Redis (HTTP 200/201)"]
IdemEngine -->|"Trùng khóa nhưng Lệch Payload"| Problem409["Trả lỗi RFC 9457 application/problem+json (HTTP 409 Conflict)"]
IdemEngine -->|"Request mới hợp lệ"| Handler["Application Handler (FastAPI 0.115+ / Go 1.24+)"]
Handler -->|"Truy vấn danh sách"| KeysetDB["PostgreSQL 17+ Keyset Pagination trên chỉ mục UUIDv7 (RFC 9562)"]
Handler -->|"Lỗi xác thực miền"| Problem422["Trả lỗi RFC 9457 Problem Details (HTTP 422 Unprocessable Content)"]
1.1. Quy hoạch Resource-Oriented URI, danh từ số nhiều và định danh thời gian UUIDv7 (RFC 9562)
Trong thiết kế hướng tài nguyên (Resource-Oriented Architecture), đường dẫn URI đại diện cho danh từ định danh thực thể nghiệp vụ (Resource Nouns), còn hành động tác động lên thực thể đó được biểu đạt hoàn toàn qua phương thức HTTP (HTTP Verb):
- Sử dụng danh từ số nhiều (
Plural Nouns) dạngkebab-casenhất quán:- Chuẩn mực:
/v1/organizations/{org_id}/payment-intents/{intent_id} - Cấm kỵ:
/v1/getOrganizationPaymentIntent,/v1/payment_intent(số ít/snake_case trong đường dẫn) hoặc lồng quá3cấp phân cấp trên một URI.
- Chuẩn mực:
- Cách biểu diễn các hành động nghiệp vụ không ánh xạ trực tiếp vào CRUD (
Sub-resource Actions): Khi cần thực thi các chuyển đổi trạng thái phức tạp như khóa thẻ tín dụng, hoàn tiền một phần hoặc phê duyệt chứng từ, thay vì nhét động từ vào gốc URI, hãy mô hình hóa hành động đó như một tài nguyên con (Sub-resource Noun) hoặc một điểm cuối điều khiển dưới tài nguyên cha:POST /v1/payment-intents/{intent_id}/cancellations(Tạo một yêu cầu hủy giao dịch)POST /v1/invoices/{invoice_id}/issuance(Phát hành hóa đơn điện tử)
- Thay thế
Auto-Increment BIGINTvàUUIDv4bằng định danh thời gianUUIDv7(IETF RFC 9562):- Nếu công khai khóa chính tự tăng (
id = 10452) lên URI/v1/orders/10452, hệ thống sẽ lộ tổng quy mô giao dịch cho đối thủ cạnh tranh và tạo điều kiện cho kẻ tấn công quét vét cạn tài nguyên theo lỗ hổng BOLA đã phân tích kỹ trong bài bảo mật Web API theo tiêu chuẩn OWASP API Security Top 10. - Ngược lại, nếu dùng
UUIDv4ngẫu nhiên hoàn toàn (122 bitsngẫu nhiên), mỗi bản ghi mới chèn vào PostgreSQL/MySQL sẽ rơi vào vị trí ngẫu nhiên trên cây B-Tree, gây phân mảnh trang dữ liệu (Page Splitting) và làm sụt giảm nghiêm trọng thông lượng ghi IOPS. - Chuẩn IETF RFC 9562
UUIDv7giải quyết trọn vẹn cả hai bài toán:48 bitsđầu tiên mã hóa dấu thời gian Unix Epoch độ phân giải mili-giây (unix_ts_ms) theo thứ tự tăng dần tự nhiên (k-sortable), tiếp theo là4 bitsphiên bản (0111),2 bitsbiến thể và74 bitsngẫu nhiên mật mã học. Nhờ đó, định danh0192f8a0-7c4b-7d92-8a10-4b3e9c1d2f01vừa không thể bị đoán trước, vừa chèn tuần tự vào cuối nhánh phải của cây B-Tree và dùng làm con trỏ phân trang trực tiếp mà không cần thêm cột thứ hai!
- Nếu công khai khóa chính tự tăng (
1.2. Ngữ nghĩa phương thức HTTP chuẩn IETF RFC 9110 và cơ chế Idempotency-Key cho giao dịch đột biến
Theo đặc tả nền tảng IETF RFC 9110 (HTTP Semantics, Mục 9.2), mọi kỹ sư thiết kế API phải nắm vững hai thuộc tính toán học của phương thức HTTP để cấu hình cơ chế thử lại (Retry Policy) tại API Gateway và Client SDK:
- Safe (An toàn —
GET,HEAD,OPTIONS): Chỉ đọc trạng thái tài nguyên, tuyệt đối không gây ra bất kỳ thay đổi trạng thái nào trên máy chủ. - Idempotent (Lũy đẳng —
GET,HEAD,OPTIONS,PUT,DELETE): Gửi cùng một yêu cầu $N$ lần liên tiếp ($N ge 1$) phải để lại trạng thái tài nguyên trên máy chủ giống hệt như khi gửi thành công duy nhất1lần.
| Phương thức HTTP | Safe (Chỉ đọc)? | Idempotent (Lũy đẳng tự nhiên)? | Cacheable (Bởi CDN/Proxy)? | Ngữ nghĩa Chuẩn Enterprise & Quy tắc Bắt buộc (RFC 9110) |
|---|---|---|---|---|
GET |
Có | Có | Có | Truy xuất một tài nguyên hoặc tập hợp tài nguyên. Cấm tuyệt đối gửi kèm Request Body hoặc làm thay đổi dữ liệu khi gọi GET. |
POST |
Không | Không (Cần Idempotency-Key) |
Không (trừ khi có Expires/Cache-Control đặc biệt) |
Khởi tạo tài nguyên mới hoặc kích hoạt tiến trình xử lý nghiệp vụ. Khi tạo mới thành công, trả về 201 Created kèm header Location: /v1/orders/{id}. |
PUT |
Không | Có | Không | Thay thế toàn bộ biểu diễn của tài nguyên tại URI đích. Nên kết hợp header If-Match: "<etag>" để chống ghi đè mất dữ liệu (Lost Update). |
PATCH |
Không | Không mặc định (Có nếu dùng JSON Merge Patch RFC 7396) | Không | Cập nhật một phần các trường của tài nguyên hiện có (Content-Type: application/merge-patch+json). |
DELETE |
Không | Có | Không | Xóa tài nguyên đích. Gọi lần đầu trả về 204 No Content (hoặc 200 OK); gọi lại lần thứ hai trên cùng URI có thể trả 204 hoặc 404 nhưng trạng thái hệ thống không đổi. |
Vì POST không có tính lũy đẳng tự nhiên, nếu ứng dụng di động gọi POST /v1/orders nhưng bị rớt mạng đúng lúc gói tin phản hồi đang trên đường quay về, Client buộc phải gửi lại yêu cầu đó. Để ngăn chặn thảm họa trừ tiền hai lần hoặc tạo đơn hàng trùng lặp, mọi endpoint POST/PATCH đột biến dữ liệu quan trọng trong Enterprise API bắt buộc phải yêu cầu header Idempotency-Key: <UUIDv4/UUIDv7> kết hợp với máy trạng thái khóa phân tán nguyên tử như đã trình bày chi tiết tại bài thiết kế kiến trúc Distributed Locking và Idempotency Key với Redis và PostgreSQL.
sequenceDiagram
autonumber
participant ClientApp as Partner Client SDK
participant APIGW as API Gateway (RateLimit & Router)
participant OrderSvc as Order API Service (FastAPI 0.115+)
participant RedisStore as Redis 7.4+ Idempotency Store
participant PgDB as PostgreSQL 17+ (UUIDv7 Index)
ClientApp->>APIGW: POST /v1/orders (Idempotency-Key: idem-98a1, Body: {amount: 250000})
APIGW->>OrderSvc: Chuyển tiếp Request + W3C traceparent
OrderSvc->>RedisStore: SET idem:idem-98a1 "IN_PROGRESS|sha256_hash" NX EX 86400
RedisStore-->>OrderSvc: OK (Giữ khóa thực thi thành công)
OrderSvc->>PgDB: INSERT INTO orders (id=UUIDv7(), ...) RETURNING *
PgDB-->>OrderSvc: Trả về bản ghi Order 0192f8a0-...
OrderSvc->>RedisStore: Lưu Snapshot phản hồi (Status 201 + JSON Body) TTL 24h
OrderSvc-->>APIGW: HTTP 201 Created + Location + Sunset (RFC 8594) + RateLimit
APIGW-->>ClientApp: Trả phản hồi HTTP 201 Created
Note over ClientApp,APIGW: Giả sử mạng chập chờn, ClientApp tự động Retry cùng Idempotency-Key
ClientApp->>APIGW: POST /v1/orders (Idempotency-Key: idem-98a1, Cùng Body)
APIGW->>OrderSvc: Chuyển tiếp Request Retry
OrderSvc->>RedisStore: GET idem:idem-98a1 (Khớp mã băm SHA-256)
RedisStore-->>OrderSvc: Trả ngay Snapshot phản hồi đã lưu
OrderSvc-->>ClientApp: HTTP 201 Created + Header Idempotent-Replayed: true (Không chạm vào PgDB!)
2. Chuẩn hóa Envelope phản hồi, Keyset Cursor Pagination và lọc dữ liệu quy mô lớn
Khi một bảng giao dịch vượt ngưỡng 10,000,000 bản ghi, cơ chế phân trang của API chính là ranh giới quyết định hệ thống giữ được độ trễ phản hồi p99 < 15ms hay làm treo toàn bộ cụm cơ sở dữ liệu.
2.1. Tại sao Offset Pagination (LIMIT/OFFSET) gây sập Database ở chiều sâu hàng triệu bản ghi?
Trong các hướng dẫn cơ bản, phân trang thường được viết dưới dạng GET /v1/orders?page=5000&page_size=20, biên dịch xuống SQL thành câu lệnh:
SELECT * FROM orders ORDER BY created_at DESC LIMIT 20 OFFSET 100000;
Cơ chế LIMIT / OFFSET mang hai khuyết tật chí mạng trên môi trường Production:
- Độ phức tạp thời gian tuyến tính $O(N)$ gây nghẽn I/O và CPU: Để trả về
20dòng ở vị tríOFFSET 100000, bộ máy thực thi của PostgreSQL hoặc MySQL vẫn phải duyệt qua chỉ mục, nạp và đếm đủ100,020bản ghi, sau đó vứt bỏ100,000bản ghi đầu tiên rồi mới trả về20bản ghi cuối! Khi một tác nhân thu thập dữ liệu (hoặc đối tác đồng bộ báo cáo) quét tới trang thứ20,000(OFFSET 400000), mỗi truy vấn đơn lẻ sẽ ngốn hàng trăm mili-giây CPU. - Lỗi trôi trang (
Page Drift— Lặp hoặc Bỏ sót bản ghi): Nếu trong lúc Client đang chuyển từpage=1sangpage=2, có5đơn hàng mới vừa được chèn vào đầu bảng (ORDER BY created_at DESC), toàn bộ khung cửa sổOFFSET 20sẽ bị đẩy lùi xuống5vị trí, khiến Client nhận lại5đơn hàng cũ đã xuất hiện ở trang 1!
Giải pháp bắt buộc cho Enterprise API là Keyset Pagination (còn gọi là Seek / Cursor-Based Pagination) dựa trên định danh thứ tự thời gian UUIDv7 hoặc khóa tổ hợp (created_at, id). Thay vì đếm số dòng bỏ qua, cơ sở dữ liệu nhảy trực tiếp tới vị trí nút lá trên cây B-Tree với độ phức tạp logarit $O(log N)$ thông qua mệnh đề WHERE (created_at, id) < (:cursor_ts, :cursor_id). Dù bảng có 10 nghìn hay 1 tỷ dòng, thời gian lấy trang thứ 50,000 vẫn ổn định tuyệt đối ở mức < 1.5 ms!
-- file: keyset_pagination_benchmark_pg17.sql
-- Engine: PostgreSQL 17+ | Đối chiếu hiệu năng OFFSET vs Keyset Cursor Pagination
-- 1. Tạo bảng giao dịch chuẩn Enterprise sử dụng khóa chính UUIDv7 (RFC 9562)
CREATE TABLE IF NOT EXISTS enterprise_orders (
id UUID PRIMARY KEY, -- Sinh bằng UUIDv7 tăng dần theo thời gian
organization_id UUID NOT NULL,
status VARCHAR(32) NOT NULL,
total_cents BIGINT NOT NULL,
currency CHAR(3) NOT NULL DEFAULT 'VND',
created_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp()
);
-- 2. Chỉ mục B-Tree bao phủ (Covering Composite Index) phục vụ Keyset Seek theo Tenant
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_orders_org_keyset_desc
ON enterprise_orders (organization_id, created_at DESC, id DESC)
INCLUDE (status, total_cents, currency);
-- ❌ ANTI-PATTERN: Offset Pagination ở trang sâu (Quét và vứt bỏ 500,000 bản ghi!)
-- EXPLAIN (ANALYZE, BUFFERS) cho thấy Rows Removed by Index Recheck / Scan cực lớn
EXPLAIN (ANALYZE, BUFFERS)
SELECT id, status, total_cents, currency, created_at
FROM enterprise_orders
WHERE organization_id = '0192f000-0000-7000-8000-000000000001'
ORDER BY created_at DESC, id DESC
LIMIT 20 OFFSET 500000;
-- ✅ PRODUCTION STANDARD: Keyset Seek Pagination sử dụng Row Constructor Comparison
-- Lấy 21 bản ghi (limit + 1) để xác định chính xác cờ has_more mà không cần chạy lệnh COUNT(*)!
EXPLAIN (ANALYZE, BUFFERS)
SELECT id, status, total_cents, currency, created_at
FROM enterprise_orders
WHERE organization_id = '0192f000-0000-7000-8000-000000000001'
AND (created_at, id) < ('2026-10-16T10:15:30.412Z'::timestamptz, '0192f8a0-7c4b-7d92-8a10-4b3e9c1d2f01'::uuid)
ORDER BY created_at DESC, id DESC
LIMIT 21;
2.2. Thiết kế Opaque Base64URL Cursor, Sparse Fieldsets và Filtering đa chiều
Khi trả kết quả phân trang về cho Client, tuyệt đối không phơi bày trực tiếp tên cột cơ sở dữ liệu ra tham số truy vấn (ví dụ ?after_created_at=...&after_internal_pk=...). Thay vào đó, hãy đóng gói tọa độ trang cuối cùng vào một chuỗi Opaque Cursor mã hóa Base64URL (?cursor=eyJ0cyI6IjIwMjYtMTAtMTZUMTA6MTU6MzAuNDEyWiIsImlkIjoiMDE5MmY4YTAtN2M0Yi03ZDkyLThhMTAtNGIzZTljMWQyZjAxIn0&limit=20):
- Lợi ích kiến trúc: Phía Client chỉ cần coi
next_cursorlà một chuỗi hộp đen và truyền nguyên vẹn vào lần gọi tiếp theo. Nếu sau này đội ngũ Backend chuyển tầng lưu trữ từ PostgreSQL sang Elasticsearch hoặc thay đổi khóa sắp xếp bên dưới, giao diện hợp đồng API hoàn toàn không bị phá vỡ. - Tối ưu băng thông với Sparse Fieldsets & Filtering chuẩn: Cho phép Client chỉ định rõ các trường cần lấy (
GET /v1/orders?fields=id,status,total_cents&filter[status]=PAID,SETTLED&sort=-created_at) kết hợp với bộ đệm điều kiệnETag/If-None-Match(trả vềHTTP 304 Not Modifiedkhông tốn băng thông truyền lại JSON nếu tài nguyên chưa thay đổi).
3. Chuẩn hóa HTTP Status Codes và cấu trúc báo lỗi RFC 9457 (application/problem+json)
Một trong những phản mẫu (Anti-Pattern) tai hại nhất trong lịch sử phát triển Web API là trả về mã trạng thái HTTP 200 OK cho mọi yêu cầu rồi nhét mã lỗi tự chế vào bên trong thân JSON: {"code": 5001, "success": false, "message": "Database connection timeout"}. Cách làm này phá hủy hoàn toàn hạ tầng mạng hiện đại: CDN Cloudflare có thể lưu đệm nhầm phản hồi lỗi như một trang thành công, bộ ngắt mạch (Circuit Breaker) của Envoy/Istio không biết dịch vụ đang sập để cắt tải, và hệ thống cảnh báo SRE dựa trên tỷ lệ lỗi HTTP 5xx trở nên vô dụng.
3.1. Phân ranh giới chính xác giữa nhóm mã trạng thái 2xx, 4xx và 5xx trong hệ thống phân tán
Để các thành phần trung gian (API Gateway, Service Mesh, Client Retry Interceptor) vận hành tự động chính xác, API phải tuân thủ phân loại mã trạng thái theo IETF RFC 9110:
| Mã HTTP Status | Phân loại Trách nhiệm | Client Có Nên Tự Động Retry? | Ngữ cảnh Sử dụng Chuẩn xác trong Enterprise API |
|---|---|---|---|
200 OK |
Thành công | Không cần | Yêu cầu GET, PUT, PATCH thành công và có trả về dữ liệu biểu diễn trong Response Body. |
201 Created |
Thành công (Tạo mới) | Không cần | Tạo mới tài nguyên thành công qua POST; bắt buộc đính kèm header Location trỏ tới URI của tài nguyên mới. |
202 Accepted |
Thành công (Xử lý bất đồng bộ) | Poll theo Location |
Yêu cầu hợp lệ đã được đưa vào hàng đợi xử lý nền (ví dụ xuất báo cáo lớn); trả kèm URI kiểm tra tiến độ. |
204 No Content |
Thành công (Không thân) | Không cần | Thực thi DELETE hoặc cập nhật trạng thái thành công mà không cần trả về nội dung JSON. |
400 Bad Request |
Lỗi Cú pháp từ Client | Cấm Retry | JSON sai cú pháp, thiếu header bắt buộc, hoặc kiểu dữ liệu trên đường truyền không thể giải mã (Malformed Syntax). |
401 Unauthorized |
Chưa xác thực danh tính | Chỉ Retry sau khi Refresh Token | Thiếu Access Token, Token hết hạn hoặc chữ ký số JWT EdDSA/ES256 không hợp lệ. |
403 Forbidden |
Không đủ quyền hạn (AuthZ) |
Cấm Retry | Đã xác thực danh tính thành công nhưng tài khoản/scope không có quyền truy cập tài nguyên này (BOLA/RBAC). |
404 Not Found |
Không tồn tại tài nguyên | Cấm Retry | URI không khớp hoặc định danh tài nguyên UUIDv7 không tồn tại (hoặc bị ẩn vì lý do bảo mật đa khách hàng). |
409 Conflict |
Xung đột trạng thái tài nguyên | Không Retry nguyên trạng | Xung đột khóa lạc quan (Optimistic Locking), trùng khóa nghiệp vụ duy nhất, hoặc gửi lại Idempotency-Key với Payload khác. |
422 Unprocessable Content |
Lỗi Quy tắc Nghiệp vụ (Semantic) | Cấm Retry | Cú pháp JSON hoàn toàn đúng chuẩn nhưng vi phạm quy tắc miền (ví dụ: ngày giao hàng nhỏ hơn ngày đặt hàng, số dư ví không đủ). |
429 Too Many Requests |
Vượt hạn ngạch Rate Limit | Có (Chờ theo Retry-After) |
Client gửi quá số lượng yêu cầu cho phép trong khung cửa sổ thời gian; bắt buộc trả kèm header Retry-After. |
500 / 502 / 503 / 504 |
Lỗi Hệ thống Máy chủ / Hạ nguồn | Có (Exponential Backoff + Jitter, nếu phương thức lũy đẳng) | Lỗi nội bộ chưa xử lý (500), lỗi cổng trung gian (502), dịch vụ bảo trì/quá tải (503) hoặc quá thời gian chờ hạ nguồn (504). |
3.2. Áp dụng chuẩn IETF RFC 9457 Problem Details thay thế các định dạng lỗi tự phát
Khi xảy ra bất kỳ lỗi 4xx hoặc 5xx nào, thay vì để mỗi đội phát triển tự nghĩ ra một cấu trúc JSON khác nhau ({"err": "..."}, {"error_msg": "..."}, {"fault": {...}}), chuẩn công nghiệp năm 2026 bắt buộc sử dụng đặc tả IETF RFC 9457 (Problem Details for HTTP APIs — phát hành thay thế và nâng cấp cho RFC 7807) với kiểu phương tiện trả về là Content-Type: application/problem+json.
Một đối tượng RFC 9457 Problem Details tiêu chuẩn bao gồm 5 trường gốc và cho phép mở rộng các thuộc tính chuyên biệt (Extension Members):
type(URI Reference): Đường dẫn định danh duy nhất loại lỗi trong từ điển lỗi của doanh nghiệp (ví dụ:/problems/insufficient-wallet-balancehoặcurn:infoteck:problem:insufficient-wallet-balance).title(string): Mô tả ngắn gọn, cố định theo từng loạitype(không thay đổi theo từng request riêng lẻ).status(integer): Mã trạng thái HTTP lặp lại để tiện xử lý khi payload đi qua các tầng proxy.detail(string): Giải thích cụ thể nguyên nhân lỗi của đúng lần gọi hiện tại, giúp lập trình viên tích hợp biết cách khắc phục ngay lập tức.instance(URI Reference): Đường dẫn của chính request hoặc mã định danh truy vết cụ thể (urn:uuid:...).- Các trường mở rộng chuẩn doanh nghiệp (
Extensions):trace_id(mã W3C Trace Context để đối chiếu trực tiếp với hệ thống Logging/Jaeger),timestamp(ISO-8601 UTC), và mảngerrors[]chứa con trỏ JSON Pointer (RFC 6901) chỉ rõ từng trường dữ liệu không hợp lệ.
4. Chiến lược Quản lý Phiên bản API (API Versioning) và Vòng đời Deprecation chuẩn RFC 8594
Không một thiết kế API nào có thể giữ nguyên bất biến mãi mãi. Khi quy trình nghiệp vụ thay đổi đòi hỏi phải sửa đổi cấu trúc dữ liệu không tương thích ngược (Breaking Changes), một chiến lược API Versioning rõ ràng là chốt chặn duy nhất bảo vệ các hệ thống đối tác đang chạy trên Production.
4.1. So sánh kiến trúc URI Path Versioning, Header Versioning (Media Type) và Query Parameter
Trong thực tế triển khai cổng API doanh nghiệp, có 3 phương pháp định tuyến phiên bản chính với những đánh đổi kỹ thuật rất rõ rệt:
| Tiêu chí Kiến trúc | 1. URI Path Versioning (/v1/orders, /v2/orders) |
2. Header / Media-Type Versioning (X-API-Version: 2026-11-01 hoặc Accept: application/vnd.infoteck.v2+json) |
3. Query Parameter Versioning (/orders?version=2) |
|---|---|---|---|
| Khả năng định tuyến tại API Gateway / Nginx | Xuất sắc: Định tuyến trực tiếp bằng tiền tố đường dẫn (location /v1/ vs location /v2/) lên các cụm Microservice độc lập. |
Trung bình: Cần cấu hình quy tắc kiểm tra Header phức tạp hơn trên Kong/Envoy. | Kém: Dễ bị bỏ sót hoặc xung đột với các tham số lọc nghiệp vụ. |
| Tương thích bộ đệm CDN / HTTP Cache | Tự nhiên 100%: URL khác nhau tạo ra khóa Cache (Cache Key) tách biệt hoàn toàn mà không cần cấu hình phụ. |
Đòi hỏi cẩn trọng: Bắt buộc phải trả kèm header Vary: Accept, X-API-Version (RFC 9111), nếu quên sẽ gây rò rỉ dữ liệu sai phiên bản giữa các Client! |
Thường bị các CDN mặc định loại bỏ chuỗi truy vấn (Ignore Query String) nếu không cấu hình riêng. |
| Trải nghiệm Lập trình viên (DX & cURL/Browser) | Rất trực quan: Nhìn vào URL trong log hoặc tài liệu là biết ngay đang gọi phiên bản nào. | Giữ cho URI tài nguyên cố định chuẩn học thuật REST, nhưng khó kiểm tra nhanh trên trình duyệt. | Trông thiếu chuyên nghiệp cho các hệ thống lõi. |
| Khuyến nghị Chuẩn Enterprise 2026 | Dùng cho Phiên bản Lớn (Major Version /v1/, /v2/) khi thay đổi toàn diện mô hình tài nguyên. |
Kết hợp Date-Based Header (Infoteck-Version: 2026-11-01) cho các bản cập nhật nhỏ theo mô hình của Stripe & GitHub. |
Không khuyến nghị cho Production API. |
[!IMPORTANT]
Mô hình Lai Chuẩn Vàng 2026 (Hybrid Major-URI + Date-Header Versioning): Hãy đặt phiên bản lớn trên đường dẫn (/v1/orders) để dễ dàng tách cụm triển khai hạ tầng, đồng thời hỗ trợ header ghim phiên bản theo ngày phát hành (X-API-Version: 2026-11-01) cho những thay đổi cấu trúc nhỏ mà không buộc đối tác phải sửa đổi toàn bộ gốc URL trong mã nguồn của họ.
4.2. Quản trị Breaking Changes và tự động hóa cảnh báo ngừng hỗ trợ với HTTP Deprecation & Sunset (RFC 8594)
Một quy trình quản trị API chuyên nghiệp bắt buộc phải phân định rõ thay đổi nào là an toàn (Non-Breaking / Backward-Compatible) và thay đổi nào làm gãy hợp đồng (Breaking Change):
- Thay đổi KHÔNG phá vỡ hợp đồng (Có thể phát hành ngay trong phiên bản hiện hành): Thêm một endpoint mới; thêm một tham số truy vấn tùy chọn (
Optional Query Parameter) có giá trị mặc định; thêm một trường dữ liệu mới vào JSON phản hồi (với điều kiện Client tuân thủ nguyên lý Tolerant Reader — bỏ qua các trường lạ chưa biết). - Thay đổi PHÁ VỠ hợp đồng (Bắt buộc phải tăng phiên bản API mới): Xóa hoặc đổi tên một trường trong JSON phản hồi; thay đổi kiểu dữ liệu của một trường (ví dụ chuyển
"amount": "150000"từ chuỗi sang số nguyên150000hoặc từ đối tượng phẳng sang mảng); thêm một trường bắt buộc (Required Field) mới vào Request Body; hoặc thu hẹp điều kiện hợp lệ của đầu vào.
Khi một phiên bản API cũ (ví dụ /v1/orders bản 2025-06-01) bước vào giai đoạn chuẩn bị loại bỏ, tuyệt đối không tắt đột ngột máy chủ. Thay vào đó, hãy phát tín hiệu cảnh báo tự động ngay trong HTTP Response Headers theo chuẩn IETF RFC 8594 (Sunset) và header Deprecation trong suốt thời gian ân hạn (6–12 tháng):
Deprecation: @1777593600(Dấu thời gian Unix Epoch cho biết phiên bản này đã bị đánh dấu lỗi thời).Sunset: Wed, 01 Sep 2027 00:00:00 GMT(RFC 8594 — Thời điểm chính xác máy chủ sẽ ngừng phục vụ phiên bản này và trả về410 Gone).Link: </docs/api/migrations/v1-to-v2>; rel="sunset"(Đường dẫn tài liệu hướng dẫn nâng cấp tự động hiển thị trong công cụ giám sát của đối tác).
4.3. Giám sát lưu lượng phiên bản cũ (Legacy Version Telemetry) trước thời điểm Sunset
Tại tầng API Gateway và Prometheus Metrics, mọi yêu cầu đi qua đều cần được gắn nhãn api_version và tenant_id (http_requests_total{api_version="2025-06-01", tenant_id="partner_a"}). Nhờ đó, đội ngũ Platform Engineering có thể xuất danh sách chính xác những đối tác nào vẫn còn gọi phiên bản sắp đóng trước ngày Sunset 30 ngày để chủ động hỗ trợ chuyển đổi, đảm bảo tỷ lệ gián đoạn dịch vụ bằng 0%.
5. Bảo vệ năng lực xử lý với Rate Limiting chuẩn IETF và Đặc tả OpenAPI 3.1.1 Contract-First
Một RESTful API dù được thiết kế tinh tế đến đâu cũng sẽ sụp đổ nếu một đối tác viết vòng lặp while(true) bị lỗi bắn 50,000 yêu cầu mỗi giây làm cạn kiệt tài nguyên của toàn bộ các khách hàng khác (Noisy Neighbor Problem), hoặc nếu tài liệu đặc tả API bị lệch hoàn toàn so với mã nguồn thực tế trên Production.
5.1. Thiết kế Rate Limiting đa tầng (Token Bucket / Sliding Window) và bộ Header RateLimit-*
Thay vì sử dụng các header tự phát tiền tố X-RateLimit-* đã bị IETF loại bỏ khỏi lộ trình tiêu chuẩn, các hệ thống API năm 2026 chuẩn hóa theo đặc tả IETF HTTPAPI Working Group (RateLimit & RateLimit-Policy) kết hợp cùng header Retry-After (RFC 9110):
RateLimit-Limit: 1000: Hạn ngạch tối đa cho phép trong khung cửa sổ hiện tại.RateLimit-Remaining: 842: Số lượng yêu cầu còn lại mà Client được phép gọi trước khi bị chặn.RateLimit-Reset: 18: Số giây (delta-seconds) còn lại cho đến khi hạn ngạch được làm mới hoàn toàn (tránh dùng Unix Timestamp tuyệt đối để miễn nhiễm với lỗi lệch đồng hồ NTP giữa Client và Server).RateLimit-Policy: 1000;w=60;burst=150: Mô tả rõ chính sách giới hạn tải (1000requests mỗi cửa sổ60giây, cho phép đỉnh đột biếnburstbằng thuật toán Token Bucket hoặc Sliding Window Counter trên Redis).
5.2. Triển khai Contract-First với OpenAPI 3.1.1, FastAPI 0.115+ và kiểm định Breaking Change trên CI/CD
Trong quy trình Contract-First Development, tệp đặc tả OpenAPI 3.1.1 (tương thích 100% với chuẩn JSON Schema Draft 2020-12, hỗ trợ cú pháp kiểu rỗng tự nhiên type: ["string", "null"] thay vì thuộc tính nullable: true cũ của OpenAPI 3.0) được viết và duyệt kiến trúc trước khi gõ dòng mã đầu tiên.
Dưới đây là đặc tả OpenAPI 3.1.1 chuẩn Production tích hợp đầy đủ UUIDv7, Idempotency-Key, RateLimit-* và cấu trúc báo lỗi RFC 9457 ProblemDetails:
# file: openapi-orders-v1.yaml
# Specification: OpenAPI 3.1.1 (JSON Schema 2020-12 Compatible)
openapi: 3.1.1
info:
title: Infoteck Enterprise Order & Settlement API
version: 2026.11.0
summary: Contract-First Enterprise RESTful API theo chuẩn RFC 9110, RFC 9457, RFC 8594 & RFC 9562.
contact:
name: Infoteck Platform Architecture Team
url: https://infoteck.vn
servers:
- url: https://api.infoteck.vn/v1
description: Production API Gateway (TLS 1.3)
paths:
/orders:
get:
operationId: listOrdersKeyset
summary: Truy vấn danh sách đơn hàng bằng Keyset Cursor Pagination (O(log N))
parameters:
- name: cursor
in: query
required: false
description: Opaque Base64URL cursor trả về từ trang trước (next_cursor).
schema:
type: string
examples: ["eyJ0cyI6IjIwMjYtMTAtMTZUMTA6MTU6MzBaIiwiaWQiOiIwMTkyZjhhMC03YzRiLTdkOTItOGExMC00YjNlOWMxZDJmMDEifQ"]
- name: limit
in: query
required: false
schema:
type: integer
minimum: 1
maximum: 100
default: 20
- name: X-API-Version
in: header
required: false
schema:
type: string
default: "2026-11-01"
responses:
"200":
description: Danh sách đơn hàng phân trang theo con trỏ UUIDv7.
headers:
RateLimit-Limit:
schema: { type: integer }
RateLimit-Remaining:
schema: { type: integer }
RateLimit-Reset:
schema: { type: integer }
Deprecation:
schema: { type: string }
Sunset:
description: Thời điểm ngừng hỗ trợ phiên bản theo chuẩn IETF RFC 8594.
schema: { type: string }
content:
application/json:
schema:
$ref: "#/components/schemas/OrderCursorPage"
"429":
$ref: "#/components/responses/ProblemTooManyRequests"
post:
operationId: createOrderIdempotent
summary: Khởi tạo đơn hàng mới với bảo chứng lũy đẳng Idempotency-Key
parameters:
- name: Idempotency-Key
in: header
required: true
description: Khóa UUIDv7/UUIDv4 duy nhất cho mỗi thao tác tạo đơn (TTL 24h).
schema:
type: string
format: uuid
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateOrderRequest"
responses:
"201":
description: Khởi tạo đơn hàng thành công.
headers:
Location:
description: URI trỏ tới tài nguyên đơn hàng vừa tạo.
schema: { type: string, format: uri }
Idempotent-Replayed:
schema: { type: boolean }
content:
application/json:
schema:
$ref: "#/components/schemas/OrderResource"
"409":
$ref: "#/components/responses/ProblemConflict"
"422":
$ref: "#/components/responses/ProblemUnprocessable"
components:
schemas:
CreateOrderRequest:
type: object
required: [customer_id, currency, total_cents]
additionalProperties: false
properties:
customer_id:
type: string
format: uuid
currency:
type: string
enum: [VND, USD, SGD]
total_cents:
type: integer
minimum: 1000
note:
type: ["string", "null"]
maxLength: 500
OrderResource:
type: object
required: [id, customer_id, status, currency, total_cents, created_at]
properties:
id:
type: string
format: uuid
description: Định danh thời gian chuẩn IETF RFC 9562 UUIDv7.
customer_id:
type: string
format: uuid
status:
type: string
enum: [PENDING_PAYMENT, PAID, CANCELLED]
currency:
type: string
total_cents:
type: integer
created_at:
type: string
format: date-time
OrderCursorPage:
type: object
required: [data, pagination]
properties:
data:
type: array
items:
$ref: "#/components/schemas/OrderResource"
pagination:
type: object
required: [next_cursor, has_more, limit]
properties:
next_cursor:
type: ["string", "null"]
has_more:
type: boolean
limit:
type: integer
ProblemDetails:
description: Cấu trúc báo lỗi chuẩn IETF RFC 9457 (application/problem+json).
type: object
required: [type, title, status, detail]
properties:
type:
type: string
format: uri
title:
type: string
status:
type: integer
detail:
type: string
instance:
type: string
format: uri
trace_id:
type: string
timestamp:
type: string
format: date-time
errors:
type: array
items:
type: object
required: [pointer, reason]
properties:
pointer:
type: string
description: Con trỏ RFC 6901 JSON Pointer (ví dụ /total_cents).
reason:
type: string
responses:
ProblemConflict:
description: Xung đột trạng thái hoặc trùng Idempotency-Key khác Payload (HTTP 409).
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
ProblemUnprocessable:
description: Dữ liệu đầu vào vi phạm quy tắc miền (HTTP 422 - RFC 9110).
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
ProblemTooManyRequests:
description: Vượt quá giới hạn Rate Limit (HTTP 429).
headers:
Retry-After:
schema: { type: integer }
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetails"
Tiếp theo, chúng ta triển khai dịch vụ FastAPI 0.115+ & Pydantic v2.10+ trên Python 3.13+ tuân thủ nghiêm ngặt các quy tắc trong Clean Code Best Practices: Viết mã nguồn dễ bảo trì và mở rộng và áp dụng Clean Architecture và Domain-Driven Design trong thực tế, bao gồm bộ sinh UUIDv7 chuẩn RFC 9562, bộ giải mã Opaque Base64URL Cursor và Global Exception Handler xuất định dạng RFC 9457 application/problem+json:
# file: enterprise_api_rfc9457_cursor.py
# Runtime: Python 3.13+ | Dependencies: fastapi>=0.115.0, pydantic>=2.10.0
from __future__ import annotations
import base64
from datetime import datetime, timezone
import json
import os
import time
import uuid
from typing import Any
from fastapi import FastAPI, HTTPException, Query, Request, Response, status
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from pydantic import BaseModel, ConfigDict, Field
app = FastAPI(title="Infoteck Enterprise Order API", version="2026.11.0")
def generate_uuidv7() -> uuid.UUID:
"""
Sinh định danh thời gian chuẩn IETF RFC 9562 UUIDv7:
- 48 bits unix_ts_ms + 4 bits version (0b0111) + 12 bits rand_a
- 2 bits variant (0b10) + 62 bits rand_b
"""
ts_ms = int(time.time() * 1000) & ((1 << 48) - 1)
rand_bytes = int.from_bytes(os.urandom(10), byteorder="big")
rand_a = (rand_bytes >> 62) & 0x0FFF
rand_b = rand_bytes & ((1 << 62) - 1)
uuid_int = (ts_ms << 80) | (0x7 << 76) | (rand_a << 64) | (0x2 << 62) | rand_b
return uuid.UUID(int=uuid_int)
def encode_keyset_cursor(created_at_iso: str, record_id: str) -> str:
"""Mã hóa tọa độ phân trang thành chuỗi Opaque Base64URL không chứa dấu '='."""
payload = json.dumps({"ts": created_at_iso, "id": record_id}, separators=(",", ":")).encode("utf-8")
return base64.urlsafe_b64encode(payload).decode("ascii").rstrip("=")
def decode_keyset_cursor(cursor_token: str) -> tuple[str, str]:
"""Giải mã và kiểm chứng tính toàn vẹn của Opaque Base64URL Cursor."""
try:
padding = "=" * (-len(cursor_token) % 4)
raw_bytes = base64.urlsafe_b64decode((cursor_token + padding).encode("ascii"))
parsed = json.loads(raw_bytes.decode("utf-8"))
return str(parsed["ts"]), str(parsed["id"])
except Exception as exc:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Tham số phân trang 'cursor' không hợp lệ hoặc đã bị làm hỏng.",
) from exc
class ProblemDetailResponse(BaseModel):
"""Mô hình dữ liệu chuẩn IETF RFC 9457 (application/problem+json)."""
model_config = ConfigDict(extra="allow")
type: str
title: str
status: int
detail: str
instance: str
trace_id: str
timestamp: str
errors: list[dict[str, str]] | None = None
@app.exception_handler(RequestValidationError)
async def rfc9457_validation_exception_handler(
request: Request, exc: RequestValidationError
) -> JSONResponse:
"""Chuyển đổi lỗi xác thực Pydantic sang chuẩn RFC 9457 HTTP 422 kèm JSON Pointer."""
trace_id = request.headers.get("traceparent", f"tr-{generate_uuidv7()}")
field_errors = []
for err in exc.errors():
loc_path = "/" + "/".join(str(part) for part in err.get("loc", []) if part != "body")
field_errors.append({"pointer": loc_path or "/", "reason": err.get("msg", "Invalid value")})
problem = ProblemDetailResponse(
type="https://api.infoteck.vn/problems/validation-error",
title="Unprocessable Content",
status=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail="Một hoặc nhiều trường dữ liệu đầu vào vi phạm quy tắc hợp đồng OpenAPI 3.1.1.",
instance=str(request.url.path),
trace_id=trace_id,
timestamp=datetime.now(timezone.utc).isoformat(),
errors=field_errors,
)
return JSONResponse(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
content=problem.model_dump(exclude_none=True),
media_type="application/problem+json",
)
Để tự động gắn cảnh báo ngừng hỗ trợ RFC 8594 (Sunset), kiểm soát phiên bản theo ngày và chèn bộ header RateLimit-* trên mọi phản hồi, chúng ta thiết lập tầng HTTP Middleware tập trung như sau:
# file: api_lifecycle_and_ratelimit_middleware.py
# Runtime: Python 3.13+ | Middleware quản trị vòng đời API (RFC 8594) & IETF RateLimit
from __future__ import annotations
from typing import Callable, Awaitable
from fastapi import Request, Response
from starlette.middleware.base import BaseHTTPMiddleware
# Danh mục các phiên bản API cũ đang trong lộ trình ngừng hỗ trợ (Deprecation Schedule)
DEPRECATED_API_VERSIONS: dict[str, dict[str, str]] = {
"2025-06-01": {
"deprecation_epoch": "@1777593600",
"sunset_http_date": "Wed, 01 Sep 2027 00:00:00 GMT",
"migration_doc_url": "https://docs.infoteck.vn/api/migrations/2025-06-to-2026-11",
}
}
class EnterpriseAPIGovernanceMiddleware(BaseHTTPMiddleware):
async def dispatch(
self,
request: Request,
call_next: Callable[[Request], Awaitable[Response]],
) -> Response:
requested_version = request.headers.get("X-API-Version", "2026-11-01")
response = await call_next(request)
# 1. Luôn đính kèm phiên bản thực tế đã xử lý và Vary Header để bảo vệ CDN Cache (RFC 9111)
response.headers["X-API-Version"] = requested_version
response.headers["Vary"] = "Accept, Accept-Encoding, X-API-Version"
# 2. Gắn bộ Header điều tiết tải chuẩn IETF RateLimit
response.headers["RateLimit-Limit"] = "1000"
response.headers["RateLimit-Remaining"] = "985"
response.headers["RateLimit-Reset"] = "42"
response.headers["RateLimit-Policy"] = "1000;w=60;burst=150"
# 3. Nếu Client đang gọi phiên bản cũ, tự động phát tín hiệu Deprecation & Sunset (RFC 8594)
if requested_version in DEPRECATED_API_VERSIONS:
meta = DEPRECATED_API_VERSIONS[requested_version]
response.headers["Deprecation"] = meta["deprecation_epoch"]
response.headers["Sunset"] = meta["sunset_http_date"]
response.headers["Link"] = f'<{meta["migration_doc_url"]}>; rel="sunset"'
return response
Cuối cùng, để ngăn chặn mọi thay đổi làm gãy hợp đồng API (Breaking Changes) lọt lên nhánh main, hãy tích hợp công cụ kiểm định Spectral và oasdiff vào đường ống CI/CD:
#!/usr/bin/env bash
# file: ci_openapi_contract_gate.sh
# Chạy tự động trên mỗi Pull Request sửa đổi đặc tả OpenAPI 3.1.1 hoặc tầng HTTP Adapter
set -euo pipefail
SPEC_FILE="openapi-orders-v1.yaml"
BASE_SPEC="origin/main:openapi-orders-v1.yaml"
echo "=== [1/3] Kiểm tra cú pháp và quy tắc thiết kế OpenAPI 3.1.1 bằng Spectral ==="
npx @stoplight/spectral-cli lint "${SPEC_FILE}" --fail-severity=warn
echo "=== [2/3] Phát hiện Breaking Changes so với nhánh main bằng oasdiff ==="
git show "${BASE_SPEC}" > /tmp/base-openapi.yaml
oasdiff breaking /tmp/base-openapi.yaml "${SPEC_FILE}" --fail-on ERR
echo "=== [3/3] Xuất báo cáo Changelog hợp đồng API tự động ==="
oasdiff changelog /tmp/base-openapi.yaml "${SPEC_FILE}"
6. Checklist Kiểm Toán Thiết Kế RESTful API Doanh Nghiệp & Tài Liệu Tham Khảo Chuẩn Kỹ Thuật
6.1. Bảng đối chiếu 10 tiêu chí nghiệm thu RESTful API trước khi đưa lên Production
Trước khi phê duyệt bất kỳ điểm cuối RESTful API mới nào lên môi trường Production, kiến trúc sư hệ thống cần rà soát đủ 10 tiêu chí nghiệm thu bắt buộc sau đây:
| STT | Hạng mục Kiểm toán | Yêu cầu Kỹ thuật Bắt buộc (Pass Criteria) | Tiêu chuẩn Tham chiếu |
|---|---|---|---|
| 1 | Định danh & URI | Dùng danh từ số nhiều kebab-case; khóa định danh công khai bắt buộc là UUIDv7 (không lộ BIGINT tự tăng). |
IETF RFC 9562 |
| 2 | Ngữ nghĩa HTTP | GET tuyệt đối không sửa đổi trạng thái; PUT/DELETE đảm bảo tính lũy đẳng tự nhiên. |
IETF RFC 9110 |
| 3 | Bảo chứng Lũy đẳng | Mọi endpoint POST/PATCH tạo giao dịch quan trọng bắt buộc kiểm tra header Idempotency-Key và đối chiếu băm SHA-256. |
IETF Idempotency-Key |
| 4 | Phân trang Quy mô lớn | Sử dụng Keyset Cursor Pagination (cursor Base64URL + has_more), loại bỏ hoàn toàn OFFSET lớn trên bảng giao dịch. |
PostgreSQL 17 B-Tree Seek |
| 5 | Định dạng Báo lỗi | Trả về đúng Content-Type: application/problem+json kèm type, title, status, detail, trace_id và errors[]. |
IETF RFC 9457 |
| 6 | Mã trạng thái Chính xác | Cấm trả 200 OK khi có lỗi; phân biệt rõ 400 (cú pháp), 401 (chưa xác thực), 403 (thiếu quyền), 409 (xung đột) và 422 (nghiệp vụ). |
IETF RFC 9110 §15 |
| 7 | Quản lý Phiên bản | Áp dụng Major URI (/v1/) kết hợp Date Header (X-API-Version) và trả kèm header Vary chuẩn chỉnh. |
IETF RFC 9111 |
| 8 | Vòng đời Ngừng hỗ trợ | Phát tự động bộ ba header Deprecation, Sunset và Link: rel="sunset" tối thiểu 6 tháng trước khi đóng phiên bản cũ. |
IETF RFC 8594 |
| 9 | Điều tiết Lưu lượng | Gắn đầy đủ RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset và trả Retry-After khi chạm ngưỡng 429. |
IETF HTTPAPI RateLimit |
| 10 | Kiểm định Hợp đồng CI | Tệp đặc tả OpenAPI 3.1.1 vượt qua cổng kiểm tra tự động Spectral và oasdiff breaking --fail-on ERR trên CI/CD. |
OpenAPI v3.1.1 |
6.2. Tài liệu tham khảo & Nguồn chuẩn kỹ thuật (Primary Technical References)
- IETF RFC 9110 — HTTP Semantics (Official Internet Standard STD 97)
- IETF RFC 9457 — Problem Details for HTTP APIs (
application/problem+json, Obsoletes RFC 7807) - OpenAPI Initiative — OpenAPI Specification v3.1.1 Official Standard
- IETF RFC 8594 — The Sunset HTTP Response Header Field
- IETF RFC 9562 — Universally Unique IDentifiers (UUIDv7 Time-Ordered Specification)
- IETF HTTPAPI Working Group — RateLimit Header Fields for HTTP Specification
- Microsoft Azure Architecture Center — Web API Design Best Practices