Files
sys-analysis-design/sa-output/e-commerce/02-architecture/adr/ADR-007_cho-quyet-dinh-ownership-authz-cart-order.md
Canhchimlac 343ad8bbbc save
2026-09-15 16:02:30 +07:00

172 lines
11 KiB
Markdown

# ADR-007 — Chỗ ra quyết định ownership/authz cho Cart & Order là service (có cache Redis), không phải gateway
*Tên file: `adr/ADR-007_cho-quyet-dinh-ownership-authz-cart-order.md`*
| | |
|---|---|
| **Status** | `Proposed` |
| **Date** | 2026-09-15 |
| **Người quyết** | SA + Tech Lead (đề xuất SA, chốt SA+Tech Lead theo `decision-radar.md §5`; Security có quyền phủ quyết vì đây là quyết định authz) |
| **Người đề xuất** | SA (qua skill `sa-2-architecture`, hoạt động `adr`) |
| **Điểm radar** | **~6/10** |
| **Supersedes** | — |
| **Superseded by** | — |
| **Liên quan** | `ASR-007` · `QAS-010` · `QAS-003`/`QAS-004` (A4 xung đột dòng 1) · `RBAC_CartCheckout` (BA) §2 · `CMP-02`, `CMP-04`, `CMP-15` |
> ⚠️ **ADR bất biến sau khi `Accepted`.** Muốn đổi quyết định thì viết ADR mới có
> `Supersedes: ADR-007` và đổi trạng thái bản cũ thành `Superseded by`.
---
## 1. Bối cảnh
Mọi thao tác đọc/sửa/xoá `Cart`/`CartItem`/`Order` phải được đối chiếu quyền sở hữu: Guest theo
`session_id` của chính phiên, Customer theo `customer_id` của chính họ — 0% truy cập chéo (IDOR)
thành công (`ASR-007`, `QAS-010`). `RBAC_CartCheckout_v1.0.md §2` (BA) đã xác nhận ma trận: cả
Guest và Customer đều **không** được xem giỏ hàng/đơn hàng của người khác. Vấn đề kỹ thuật cần
chốt: **kiểm tra ownership ở đâu** (gateway hay service) và **có cache không** — vì kiểm tra mỗi
request cạnh tranh trực tiếp với ngân sách latency của `QAS-003`/`QAS-004` (đã ghi nhận xung đột
ở `QAS §A4` dòng 1).
**Ràng buộc đang chi phối:**
| Nguồn | Nội dung |
|---|---|
| `ASR-007` | 100% request qua kiểm tra ownership, 0% IDOR thành công |
| `QAS-010` | Test tự động xác nhận 0 lượt IDOR thành công |
| `QAS-003`/`QAS-004` | p95 ≤500ms (`GET /v1/cart`), p95 ≤300ms (`PATCH`/`DELETE /v1/cart/items`) — 🔴 số SA đề xuất, `OQ-018`/`OQ-019` chưa xác nhận |
| `RBAC_CartCheckout §2` (BA) | Guest theo `session_id`, Customer theo `customer_id` — ma trận đã xác nhận |
**Cái đã biết chắc / cái còn là giả định:**
| Điều | 🟢 Đã kiểm chứng / 🔴 Giả định | Bằng chứng |
|---|---|---|
| Guest/Customer không được xem giỏ/đơn của người khác | 🟢 Đã kiểm chứng | `RBAC_CartCheckout §2` (BA), đã duyệt từng phần |
| Query DB mỗi request để kiểm tra ownership sẽ vi phạm ngân sách latency `QAS-003`/`QAS-004` | 🟡 Ước lượng có cơ sở | Chưa có bài đo thật — cần đo sau khi có thiết kế cụ thể (`QAS §A4`) |
| Redis cache session/ownership đủ nhanh để giữ latency trong ngân sách | 🔴 Giả định | Chưa có POC riêng cho cache ownership |
## 2. Phương án đã cân nhắc
### PA-1 — Kiểm tra ownership tại API Gateway/ALB (trước khi vào service)
| | |
|---|---|
| **Mô tả** | Gateway giải mã JWT/session, đối chiếu ownership ngay tại lớp gateway trước khi forward request vào Cart & Order |
| **Ưu** | Chặn sớm request không hợp lệ, giảm tải cho service phía sau |
| **Nhược** | Gateway (ALB/API Gateway managed) không có quyền truy cập trực tiếp dữ liệu nghiệp vụ (`CartItem` thuộc về ai) — phải gọi ngược lại service hoặc DB để biết ownership, mất lợi thế "chặn sớm"; đặt business logic vào lớp hạ tầng dùng chung vi phạm ranh giới "cái CMP-01 KHÔNG làm" đã ghi ở `SAD §4.1` |
| **Chi phí đảo ngược** | Trung bình — chuyển logic xuống service sau này cần tách lại code đã đặt sai lớp |
### PA-2 — Kiểm tra ownership tại service (Cart & Order), query DB mỗi request
| | |
|---|---|
| **Mô tả** | Service tự đối chiếu `session_id`/`customer_id` với DB mỗi request, không cache |
| **Ưu** | Đơn giản, luôn chính xác 100% (không có dữ liệu cache cũ) |
| **Nhược** | Thêm 1 round-trip DB cho mỗi request — cạnh tranh trực tiếp với ngân sách latency `QAS-003`/`QAS-004` (đã ghi xung đột `QAS §A4`) |
| **Chi phí đảo ngược** | Thấp — thêm cache sau này là cải tiến, không phải viết lại |
### PA-3 — Kiểm tra ownership tại service, cache session/ownership trong Redis *(chọn)*
| | |
|---|---|
| **Mô tả** | Service tự đối chiếu ownership; thông tin session/ownership được cache trong Redis (TTL ngắn), chỉ query DB khi cache miss |
| **Ưu** | Giữ ranh giới đúng (authz logic ở service sở hữu dữ liệu, không phải gateway); giảm round-trip DB cho phần lớn request — cân bằng được `ASR-007` và `QAS-003`/`004` |
| **Nhược** | Thêm thành phần cache cần vận hành (đã có ElastiCache Redis trong kiến trúc — `CMP-15`); cần xử lý đúng khi cache invalidate (VD khi Guest session hết hạn, khi Customer đổi giỏ hàng) |
| **Chi phí đảo ngược** | Thấp-trung bình — bỏ cache (quay về PA-2) nếu phát hiện vấn đề nhất quán, không cần viết lại toàn bộ |
### Bảng so sánh
| Tiêu chí | PA-1 (Gateway) | PA-2 (Service, không cache) | PA-3 (Service + Redis cache) |
|---|---|---|---|
| Đúng ranh giới sở hữu logic (`SAD §4.1` "cái CMP-01 KHÔNG làm") | ❌ | ✅ | ✅ |
| Đáp ứng ngân sách latency `QAS-003`/`004` | 🔶 Chưa rõ (vẫn cần gọi ngược) | ❌ Rủi ro cao | ✅ (kỳ vọng, chưa đo) |
| Độ phức tạp vận hành thêm | Thấp | Thấp nhất | Trung bình (thêm cache) |
| Đảm bảo 0% IDOR (`ASR-007`) | ✅ (nếu gọi ngược đúng) | ✅ | ✅ (cần xử lý invalidate đúng) |
## 3. Quyết định
> **Chọn PA-3 — Kiểm tra ownership tại service (Cart & Order), cache session/ownership trong
> Redis, query DB khi cache miss.**
**Vì sao:** Đây là phương án duy nhất giữ đúng ranh giới sở hữu logic (`CMP-01` API
Gateway/ALB đã ghi rõ "không tự quyết định authz chi tiết" ở `SAD §4.1`) trong khi vẫn có cơ hội
đáp ứng ngân sách latency `QAS-003`/`004` tốt hơn PA-2.
**Phạm vi áp dụng:** Toàn bộ endpoint `GET`/`PATCH`/`DELETE /v1/cart*` và các endpoint liên quan
`Order` trong phạm vi Cart & Order Service.
### Điều kiện chuyển `Proposed → Accepted`
| Điều kiện | Ai xác nhận | Trạng thái hiện tại |
|---|---|---|
| Đo lại `QAS-003`/`QAS-004` sau khi có thiết kế cache cụ thể (theo `QAS §A4` đã ghi) | QA + Tech Lead | Chưa đo |
| Thiết kế cơ chế invalidate cache đúng khi session hết hạn/giỏ hàng đổi | Tech Lead | Chưa thiết kế |
| Không bắt buộc POC riêng (radar 6, dưới ngưỡng 8) nhưng khuyến nghị đo trước khi `Accepted` | Tech Lead | — |
## 4. Phương án bị loại và lý do
| Phương án | Loại vì | Gắn với | Điều kiện nào thì xét lại |
|---|---|---|---|
| PA-1 — Kiểm tra tại Gateway | Gateway không có quyền truy cập dữ liệu nghiệp vụ, đặt sai ranh giới logic | `SAD §4.1` cột "cái nó KHÔNG làm" của `CMP-01` | Nếu về sau có một BFF (Backend-for-Frontend) layer sở hữu session tập trung — xét lại cho riêng phần xác thực (không phải authz chi tiết theo dữ liệu) |
| PA-2 — Service, không cache | Rủi ro vi phạm ngân sách latency `QAS-003`/`004` do round-trip DB mỗi request | `QAS-003`, `QAS-004`, `QAS §A4` | Nếu đo thực tế cho thấy query DB đơn giản (index tốt) đủ nhanh mà không cần cache — đơn giản hoá bằng cách bỏ Redis, giảm 1 thành phần vận hành |
## 5. Hệ quả
**Hệ quả tích cực**
- Giữ đúng ranh giới sở hữu logic theo domain (khớp `SAD §4.1`)
- Có cơ hội đáp ứng ngân sách latency tốt hơn nhờ cache
**Hệ quả tiêu cực phải sống chung**
- Redis trở thành một điểm phụ thuộc thêm cho luồng Cart & Order — cần thiết kế đường lỗi khi
Redis chậm/hỏng (thuộc `FAIL`, hoạt động 8): fallback về query DB trực tiếp khi cache miss/lỗi
- Rủi ro dữ liệu cache cũ (stale) nếu invalidate không đúng lúc — cần kiểm thử kỹ
**Cái quyết định này khoá lại**
| Muốn đổi về sau thì | Tốn |
|---|---|
| Chuyển sang kiểm tra tại Gateway sau này | Viết lại toàn bộ luồng xác thực + đặt lại ranh giới logic — không khuyến khích |
**Việc phát sinh**
| Việc | Chủ | Hạn | Ghi ở đâu |
|---|---|---|---|
| Đo `QAS-003`/`QAS-004` với thiết kế cache cụ thể | QA + Tech Lead | GĐ3 (trước go-live) | `FIT`/bài đo GĐ3 |
| Thiết kế cơ chế invalidate cache | SA (hoạt động `sec`) + Tech Lead | GĐ2 tiếp theo | `SEC_e-commerce` §2 |
| Thiết kế fallback khi Redis lỗi/chậm | SA (hoạt động `fail`) | GĐ2 tiếp theo | `FAIL_e-commerce` |
## 6. Cách kiểm chứng quyết định này được tuân thủ
| Cách kiểm | Công cụ | Chạy ở đâu | `FIT` |
|---|---|---|---|
| Test IDOR tự động (gọi API với token/session không phải chủ sở hữu) — 0 lượt thành công | Integration test (tương ứng `AC-US003-12/13` BA) | CI + staging trước mỗi release | — (đã có ở `QAS-010`) |
| API Gateway (`CMP-01`) không chứa logic authz chi tiết theo dữ liệu | Code review + kiến trúc lint | CI | `FIT-10` (ứng viên) |
| Latency `GET`/`PATCH`/`DELETE /v1/cart*` đạt ngân sách sau khi có cache | k6 load test | Staging, trước mỗi release | — (đã có ở `QAS-003`/`004`) |
## 7. Điều kiện xét lại
| Dấu hiệu | Ngưỡng | Ai theo dõi |
|---|---|---|
| Bài đo `QAS-003`/`004` sau khi có cache vẫn không đạt ngân sách | Vượt p95 đề xuất | Tech Lead |
| Phát hiện lỗi cache invalidate gây IDOR hoặc dữ liệu cũ ảnh hưởng nghiệp vụ | Bất kỳ sự cố production | Security/SRE |
## 8. Tham chiếu
- POC: Không bắt buộc — khuyến nghị đo trước `Accepted`
- Bài đo: `QAS-003`, `QAS-004`, `QAS-010`
## 9. Review log (không đổi Status)
| Ngày | Người review | Vai trò | Quyết định | Lý do chưa chuyển `Accepted` |
|---|---|---|---|---|
| 2026-09-15 | Điều phối dự án (thay mặt Tech Lead, chế độ chạy thử) | Tech Lead (ký thay, ngoại lệ `DEC-01`) | `Reviewed (Proposed giữ nguyên)` — nội dung đủ làm cơ sở thiết kế tiếp (`icd`/`dat`/`sec`/`inf`/`fail`) | Chưa có người Security để ký (`ADL §8` việc #7) — ngoại lệ dự án chạy thử không thay được chữ ký chuyên môn Security |
> Đây là ghi nhận **review nội dung**, không phải "sign" theo nghĩa gate: `Status` giữ nguyên
> `Proposed`. Xem `00-index/ADL_e-commerce.md` (Change Log — Review 2026-09-15) và `ADL §8`
> "Việc phải làm" cho điều kiện chuyển `Accepted`. Confidence tổng thể của lượt review: 🔴 —
> `AG2` chưa ký.
- Tài liệu ngoài: `RBAC_CartCheckout_v1.0.md §2` (BA)
- Thảo luận: `ASR_e-commerce_v1.0.md §B2 ASR-007`, `QAS_e-commerce_v1.0.md §A4` (xung đột dòng 1)