174 lines
7.4 KiB
Markdown
174 lines
7.4 KiB
Markdown
# PART 2 — biến thể `api-service`
|
||
|
||
> **Dùng khi** `PRODUCT = api-service` — sản phẩm không có giao diện; người tiêu thụ là hệ
|
||
> thống khác hoặc team khác. Cắm khối này vào chỗ PART 2 của [`../srs.md`](../srs.md).
|
||
>
|
||
> **Tiêu chí G3 riêng của biến thể này**: mỗi endpoint có bảng tham số/schema đầy đủ · mọi
|
||
> mã lỗi map về endpoint · đã trả lời xong ba câu idempotency / tương thích ngược / phân
|
||
> trang · có ít nhất một team tiêu thụ đã đọc và xác nhận.
|
||
|
||
🔴 **Với loại này, `API_<US>.md` là artifact chính, không phải phụ lục.** PART 2 ở đây mô tả
|
||
*hợp đồng nhìn từ phía người tiêu thụ*; chi tiết kỹ thuật từng endpoint vẫn ở
|
||
[`../api-contract.md`](../api-contract.md). Đừng chép trùng — PART 2 trả lời "có những khả
|
||
năng gì", `API` trả lời "gọi thế nào".
|
||
|
||
---
|
||
|
||
## 2.1 Người tiêu thụ
|
||
|
||
*Thay cho "danh sách màn hình". Ai gọi API này quyết định AC viết thế nào.*
|
||
|
||
| ID | Người tiêu thụ | Là ai | Gọi để làm gì | Tần suất dự kiến | Đầu mối |
|
||
|---|---|---|---|---|---|
|
||
| CON-01 | | Hệ thống nội bộ / Đối tác ngoài / App di động | | | |
|
||
|
||
**Ba câu bắt buộc:**
|
||
|
||
| Câu hỏi | Trả lời |
|
||
|---|---|
|
||
| Có người tiêu thụ nào **ngoài tổ chức** không? | *(quyết định mức chặt của versioning và bảo mật)* |
|
||
| Người tiêu thụ có tự thử được không, hay cần môi trường sandbox? | |
|
||
| Ai được thêm người tiêu thụ mới, và bằng quy trình gì? | |
|
||
|
||
## 2.2 Danh sách khả năng
|
||
|
||
| ID | Khả năng nghiệp vụ | Endpoint | Method | Người tiêu thụ | Đồng bộ/Bất đồng bộ | BR |
|
||
|---|---|---|---|---|---|---|
|
||
| CAP-01 | Tra cứu … | `/api/v1/…` | GET | CON-01 | Đồng bộ | |
|
||
| CAP-02 | Ghi nhận … | `/api/v1/…` | POST | CON-01, CON-02 | Bất đồng bộ (trả 202 + callback) | BR-0nn |
|
||
|
||
## 2.3 Sơ đồ luồng gọi
|
||
|
||
<!-- archify: sequence · diagrams/SRS_<US>_<luong>.sequence.json -->
|
||
```mermaid
|
||
sequenceDiagram
|
||
autonumber
|
||
participant C1 as CON-01 (người gọi)
|
||
participant SVC as Service
|
||
participant DB as Cơ sở dữ liệu
|
||
participant Q as Hàng đợi
|
||
participant C2 as CON-02 (người tiêu thụ event)
|
||
|
||
C1->>SVC: POST /orders (Idempotency-Key)
|
||
SVC->>DB: Ghi bản ghi
|
||
alt Ghi thành công
|
||
DB-->>SVC: OK
|
||
SVC->>Q: publish OrderCreated
|
||
SVC-->>C1: 201 + id
|
||
Q-->>C2: OrderCreated
|
||
else Trùng Idempotency-Key
|
||
DB-->>SVC: đã tồn tại
|
||
SVC-->>C1: 200 + id cũ (không tạo bản ghi thứ hai)
|
||
else Lỗi ghi
|
||
DB--xSVC: lỗi
|
||
SVC-->>C1: 503 · E-XXX-0503 (retry được)
|
||
end
|
||
```
|
||
|
||
🔴 **Bắt buộc vẽ ba nhánh**: thành công · **gọi lại trùng** · lỗi. Nhánh giữa là nhánh chứng
|
||
minh §2.4.1 idempotency hoạt động — thiếu nó thì người tiêu thụ không biết gọi lại có an toàn không.
|
||
|
||
**Bảng đi kèm** *(quy tắc W13)*:
|
||
|
||
| Bước | Đồng bộ / Bất đồng bộ | Timeout | Retry được | Mã lỗi |
|
||
|---|---|---|---|---|
|
||
| 1 | Đồng bộ | 5s | ✅ với `Idempotency-Key` | |
|
||
| 5 | Bất đồng bộ | — | Hàng đợi tự retry 3 lần | |
|
||
|
||
Với luồng **bất đồng bộ**, bắt buộc trả lời ba câu — sơ đồ không nói được:
|
||
|
||
| Câu hỏi | Trả lời |
|
||
|---|---|
|
||
| Người gọi biết kết quả bằng cách nào | polling `GET /orders/{id}` · callback · event |
|
||
| Chờ tối đa bao lâu | |
|
||
| Quá hạn mà chưa có kết quả thì làm gì | |
|
||
|
||
---
|
||
|
||
## 2.4 CAP-01 — <Tên khả năng>
|
||
|
||
### 2.4.1 Hợp đồng
|
||
|
||
| | |
|
||
|---|---|
|
||
| **Endpoint** | `POST /api/v1/…` |
|
||
| **Quyền** | scope `…` / client `…` |
|
||
| **Idempotent** | ✅/❌ — nếu ✅: khoá idempotency là gì, giữ bao lâu |
|
||
| **Gọi lại an toàn (retry)** | ✅/❌ |
|
||
| **Thời gian phản hồi mục tiêu** | ≤ … ms (p95) |
|
||
| **Giới hạn tần suất** | … req/phút/client · vượt thì trả gì |
|
||
|
||
🔴 **Ba câu này là chỗ hay bỏ sót nhất của API spec:**
|
||
|
||
1. **Idempotency** — người gọi timeout rồi gọi lại, có tạo hai bản ghi không? Nếu không
|
||
idempotent thì phải nói rõ để người tiêu thụ tự xử lý.
|
||
2. **Retry** — lỗi nào được retry, lỗi nào không? Khuyến nghị backoff bao nhiêu?
|
||
3. **Đồng thời** — hai request cùng sửa một tài nguyên thì sao? Có optimistic locking không?
|
||
|
||
### 2.4.2 Tham số / Request
|
||
|
||
| Tên | Kiểu | Vị trí | Bắt buộc | Mặc định | Ràng buộc | BR |
|
||
|---|---|---|---|---|---|---|
|
||
| | string | query / path / body | ✅ | — | 3–20 ký tự, `^[A-Z0-9-]+$` | BR-0nn |
|
||
|
||
*Cột `Ràng buộc` là tương đương của "bảng field" ở biến thể `screen` — phải cụ thể ngang vậy.*
|
||
|
||
### 2.4.3 Response thành công
|
||
|
||
| Trường | Kiểu | Có thể null | Nghĩa nghiệp vụ | Ghi chú |
|
||
|---|---|---|---|---|
|
||
| | | | | |
|
||
|
||
### 2.4.4 Response lỗi
|
||
|
||
| HTTP | `code` | Khi nào | Người gọi nên làm gì | Mã lỗi SRS §4.1 |
|
||
|---|---|---|---|---|
|
||
| 409 | | | Không retry, sửa dữ liệu | `E-XXX-0001` |
|
||
| 503 | | | Retry với backoff | `E-XXX-0503` |
|
||
|
||
**Cột "Người gọi nên làm gì" là cột thay thế cho "hiển thị ở đâu" của biến thể `screen`.**
|
||
Không có nó thì mỗi team tiêu thụ tự đoán một kiểu xử lý lỗi.
|
||
|
||
---
|
||
|
||
## 2.5 Hợp đồng dữ liệu chung
|
||
|
||
| # | Vấn đề | Quyết định |
|
||
|---|---|---|
|
||
| 1 | **Số lớn** (id, số tiền) | `string` / `number` — vượt `2^53` thì JS làm tròn sai |
|
||
| 2 | **Thời gian** | Định dạng, múi giờ |
|
||
| 3 | **Phân trang** | offset / cursor · có `total`? có `hasNext`? |
|
||
| 4 | **Sắp xếp** | Cú pháp, trường nào cho phép |
|
||
| 5 | **Trường null vs. vắng mặt** | Có khác nghĩa không |
|
||
| 6 | **Enum** | Người tiêu thụ gặp giá trị lạ (mới thêm) thì xử lý sao |
|
||
|
||
## 2.6 Phiên bản & tương thích ngược
|
||
|
||
| | |
|
||
|---|---|
|
||
| **Cách đánh version** | URL `/v1/` · header · … |
|
||
| **Thay đổi nào là phá vỡ tương thích** | *(bỏ trường, đổi kiểu, thêm ràng buộc, đổi nghĩa mã lỗi)* |
|
||
| **Thay đổi nào là an toàn** | *(thêm trường optional, thêm giá trị enum — **chỉ khi** §2.5 #6 đã định nghĩa)* |
|
||
| **Báo trước bao lâu khi bỏ version cũ** | |
|
||
| **Chạy song song mấy version** | |
|
||
|
||
🔴 **Thêm một giá trị enum là thay đổi phá vỡ tương thích** nếu §2.5 #6 không nói người tiêu
|
||
thụ phải làm gì với giá trị lạ. Đây là lỗi tương thích phổ biến nhất và im lặng nhất.
|
||
|
||
## 2.7 Trạng thái tương đương "màn hình rỗng"
|
||
|
||
| Tình huống | Trả về gì | HTTP |
|
||
|---|---|---|
|
||
| Truy vấn hợp lệ, không có bản ghi nào | Mảng rỗng + paging `total: 0` — **không phải 404** | 200 |
|
||
| Tài nguyên không tồn tại | | 404 |
|
||
| Tài nguyên tồn tại nhưng không có quyền | 404 hay 403? *(404 giấu sự tồn tại — chọn theo mức nhạy cảm)* | |
|
||
|
||
## 2.8 Môi trường & tích hợp thử
|
||
|
||
| | |
|
||
|---|---|
|
||
| Sandbox | Có / Không — đường dẫn |
|
||
| Dữ liệu mẫu cho người tiêu thụ thử | |
|
||
| Cách cấp credential | |
|
||
| Tài liệu tích hợp bàn giao ở đâu | *(đây là `MANUAL` của GĐ5 với loại sản phẩm này)* |
|