Resource model và API contract
Một contract tốt mô tả resource và invariant mà client cần, không phơi bày schema database hay implementation detail. OpenAPI hỗ trợ thực thi contract, nhưng không thay thế quyết định semantics.
1. Resource boundaries
URI nên định danh collection, item hoặc subresource theo domain lifecycle, không chỉ sao chép bảng. /orders/{id}/items hợp khi item phụ thuộc order; command endpoint như /orders/{id}/cancel hợp khi operation không ánh xạ CRUD tự nhiên. Dù chọn cách nào, method, status và transition phải rõ.
- Không expose database schema trực tiếp: tên cột, quan hệ join và internal flags thường không phải public contract.
- Request và response có thể dùng DTO khác nhau để bảo vệ write surface và tránh mass assignment.
- Định nghĩa ownership/tenant ở resource-level, không để client tự quyết định object scope qua một ID.
2. Schema, validation và errors
Schema cần nói rõ required/optional/null, enum, precision, timezone, identifier format và unknown-field policy. Validation cú pháp nằm ở boundary; business invariant và authorization cần current state trong use case. Normalize/canonicalize trước comparison chỉ khi quy tắc được tài liệu hóa.
{
"code": "VALIDATION_FAILED",
"message": "Request contains invalid fields",
"traceId": "req-7f3a",
"violations": [{"field":"currency","reason":"unsupported"}]
}
Field errors phải machine-readable nhưng không leak SQL, stack trace, token hay thông tin giúp attacker enumerate resource.
3. Filtering và sorting an toàn
Cho phép allow-list field/operator thay vì ghép query tùy ý. Values luôn parameterize; identifiers được map qua allow-list. Giới hạn query complexity, page size, regex/costly filter và số quan hệ expand để tránh unrestricted resource consumption. Sorting cần deterministic tie-breaker, thường là id, để pagination không đảo thứ tự khi nhiều row bằng nhau.
4. Pagination và consistency
| Strategy | Ưu điểm | Trade-off cần nói |
|---|---|---|
| Offset/page | Dễ hiểu, jump tới trang | Deep offset chậm; insert/delete giữa các page gây drift/duplicate |
| Cursor/keyset | Ổn định và nhanh theo index ở tập lớn | Cursor opaque; cần encode order/filter/version và giới hạn sửa |
| Snapshot token | Rõ consistency giữa nhiều page | Tốn storage/retention; phải định nghĩa expiry và stale behavior |
Cursor nên chứa giá trị order cuối, filter fingerprint và version; ký hoặc mã hóa khi client không được sửa. Response cần nextCursor, max limit và tuyên bố dữ liệu có thể thay đổi giữa các page hay không.
5. Compatibility và versioning
Additive optional field thường tương thích nếu client bỏ qua unknown fields, nhưng đổi type/meaning, biến optional thành required hoặc thêm enum mà client không chịu unknown có thể breaking. Tolerant reader không phải giấy phép cho server mơ hồ.
- Giữ deprecation window, usage telemetry, migration guide và owner cho từng version.
- URI, header hoặc media-type versioning đều có trade-off; chọn theo ecosystem và khả năng cache/observability.
- OpenAPI là executable contract cho docs, generated clients, mock và contract tests; design review vẫn cần kiểm tra semantics, auth và failure.
6. Bulk và asynchronous operation
Bulk endpoint phải công bố atomicity (all-or-nothing hay per-item), thứ tự, giới hạn kích thước và outcome từng item. Long-running operation nên trả 202 Accepted cùng operation resource để poll trạng thái/cancel. Webhook callback cần signature, replay protection, bounded retry và idempotency.