feat: rename COUNTER to TRACKING and update event type documentation and HLD architecture specs

This commit is contained in:
2026-07-29 14:26:39 +07:00
parent f33a1e05ad
commit 064ef0cb3c
10 changed files with 2136 additions and 223 deletions

View File

@@ -1,7 +1,3 @@
# Phân tích Use Cases - Event Processor Service
Dựa trên các trao đổi và hệ sinh thái hiện tại của nền tảng, tài liệu này đã được cập nhật lại để làm rõ luồng xử lý và kiến trúc cho 13 Use Cases.
## 1. Các Thành phần Hệ thống Hiện tại (System Topology)
Dưới đây là các component đang có sẵn và vai trò của chúng trong luồng xử lý sự kiện:
- **Nguyên tắc cốt lõi:** Các giao dịch tài chính/điểm thưởng cần sự chính xác, an toàn tuyệt đối.
@@ -9,10 +5,10 @@ Dưới đây là các component đang có sẵn và vai trò của chúng trong
- **PostgreSQL:** Database chính yếu (lưu trữ an toàn, ACID).
- **Redis:** Hiện tại CHỈ làm cache server, chỉ dùng khi thực sự cần thiết (Không dùng làm primary data store cho counter/state quan trọng).
- **Doris:** Data Warehouse (Dùng cho BI, Dashboard, Reporting, Segment).
- **transaction-service:** Core Engine của hệ thống (Xử lý giao dịch tài chính, **Rule Engine**, cộng/trừ điểm, tặng voucher, xử lý các bộ đếm - counter). Lắng nghe (consume) từ Kafka `events-topic`.
- **transaction-service:** Core Engine của hệ thống (Xử lý giao dịch tài chính, **Rule Engine**, cộng/trừ điểm, tặng voucher, xử lý các bộ đếm Rule counter, giới hạn thưởng). Lắng nghe (consume) từ Kafka `events-topic`.
- **event-processor-service:** Đóng vai trò **Event Gateway/Router**. Nhận event từ các nguồn (Client/Service) -> Validate/Định tuyến -> Đẩy vào Kafka `events-topic`.
- **notification-service:** Chịu trách nhiệm push noti (SMS/Email/Firebase). Lắng nghe từ Kafka `noti-topic`.
- **marketing-service:** Chịu trách nhiệm lập lịch (Scheduler), quét tập khách hàng theo chiến dịch.
- **marketing-service:** Lập lịch (Scheduler), quét tập khách hàng theo chiến dịch.
- **attribute-service:** Quản lý Dynamic Attribute (mô hình EAV) để tạo thuộc tính động cho Customer, Card, Product_Account, Items
- **member-app & merchant-app:** Ứng dụng Frontend cho khách hàng và đối tác.
@@ -38,99 +34,6 @@ Dưới đây là các component đang có sẵn và vai trò của chúng trong
---
## 3. Đánh giá lại Kiến trúc (Re-evaluation)
Với hệ sinh thái hiện tại (đặc biệt: `transaction-service` là Rule Engine + Counter, và `Redis` chỉ làm cache), vai trò của `event-processor-service` đã thay đổi so với góc nhìn ban đầu. Nó KHÔNG phải là nơi tính toán Rule (Rule Engine), mà là **Cửa ngõ tiếp nhận và định tuyến sự kiện (Event Ingestor & Router)**.
Dưới đây là đánh giá lại phương án xử lý cho từng nhóm vấn đề:
### Vấn đề 1: Xử lý Login & Counter (UC-01 & UC-02)
- **Ràng buộc:** Event `Login` có tần suất rất cao (nhiều lần/ngày/user). Nếu đẩy tất cả vào `transaction-service` sẽ gây nghẽn Core Engine. Tuy nhiên, UC-01 lại yêu cầu realtime cho lần đầu.
- **Phương án điều chỉnh (Tách biệt logic qua Attribute Service):**
- **UC-01 & UC-02 (First Login & Daily Login):** Đều sử dụng chung event `Login` (Loại **Counter Event**), được đẩy vào `tracking-topic`.
- `attribute-service` sẽ hứng `Login` và thực hiện đồng thời 2 rule:
- **First Login Bonus (UC-01):** Sử dụng instruction `SET_TRUE_ONCE` trên attribute `first_login_flag`. Nếu gán True thành công (tức là đăng nhập lần đầu), hệ thống sẽ trigger event `First_Login_Reward` gửi vào `events-topic` để TP cộng 100 điểm ngay lập tức.
- **Daily Login Counter (UC-02):** Tính toán `last_login_date``current_streak`. Tránh được việc lưu rác vào TP (Idempotent nếu login nhiều lần 1 ngày). Khi streak chạm 7 hoặc 30 ngày, bắn trigger event (`Login_Streak_7_Days`) vào `events-topic`.
### Vấn đề 2: Xử lý Tracking & Counter Events — Pattern "Accumulate & Trigger" (UC-03 & UC-12)
- **Ràng buộc:** `transaction-service` xử lý giao dịch tài chính. Tuyệt đối không để các event theo dõi hành vi (tracking) làm nghẽn luồng tài chính cốt lõi.
- **Ý kiến của anh:** Cần có giải pháp để xác định event thuộc loại **counter** (có thể cân nhắc Dynamic Attribute), phân loại vào topic riêng. Chỉ khi đạt mức cấu hình (threshold) thì mới trigger gửi vào `events-topic` để `transaction-service` xử lý.
- **Phương án: Pattern "Accumulate & Trigger" dùng Dynamic Attribute**
**Ý tưởng cốt lõi:** `event-processor-service` phân loại event thành 2 nhóm:
- **Direct Events** (Login, Redeem, Transfer...): Đẩy thẳng `events-topic``transaction-service`.
- **Counter Events** (ViewItem, RedeemItem count...): Đẩy vào `tracking-topic` → tích lũy counter qua `attribute-service` → chỉ khi chạm ngưỡng mới trigger `events-topic`.
**Luồng xử lý chi tiết:**
```
member-app ──(ViewItem)──▶ event-processor-service
│ Phân loại: Counter Event
tracking-topic (Kafka)
attribute-service
┌──────────┴──────────┐
│ 1. Tăng counter │
│ (Dynamic Attr) │
│ 2. Check threshold │
│ (cấu hình) │
└──────────┬──────────┘
Counter < threshold?
┌───────┴───────┐
│ YES │ NO (đạt ngưỡng)
▼ ▼
(dừng) events-topic (Kafka)
transaction-service
(cộng điểm/voucher)
```
**Ví dụ cụ thể cho UC-03 (View Item):**
- Cấu hình Rule: "Xem 10 sản phẩm khác nhau trong tuần → thưởng 50 điểm".
- `attribute-service` tạo Dynamic Attribute `weekly_view_count` (type: INTEGER) cho entity `Customer`.
- Mỗi event `ViewItem` → `attribute-service` tăng counter `weekly_view_count` += 1.
- Khi `weekly_view_count` >= 10 → `attribute-service` bắn event `Counter_Threshold_Reached` vào `events-topic`.
- `transaction-service` hứng event → cộng 50 điểm → reset counter.
**Ví dụ cụ thể cho UC-12 (Most Redeem Item):**
- Cấu hình Rule: Counter thuần túy, không cần trigger (chỉ cập nhật số lượng).
- `attribute-service` tạo Dynamic Attribute `total_redeem_count` (type: INTEGER) cho entity `Product` (Item).
- Mỗi event `ItemRedeemed` → `attribute-service` tăng counter → Portal query attribute này để hiển thị "Most Popular".
**Thống nhất giải pháp cho 2 câu hỏi mở (Theo hướng giảm tải TP):**
**Q1: Ai quản lý cấu hình threshold?**
- **Phương án chốt:** Đặt cấu hình threshold ngay tại **cấu hình events / metadata của Dynamic Attribute** (trong `attribute-service`), tách biệt hoàn toàn khỏi `transaction-service`.
- **Lý do & Luồng chạy:** Để giảm tải tối đa cho `transaction-service` (TP). Nhóm Event/Attribute sẽ hoạt động độc lập (self-contained).
- Attribute `weekly_view_count` sẽ lưu kèm cấu hình: `{ threshold: 10, trigger_event: "View_10_Items_Reward" }`.
- Cứ mỗi event `ViewItem`, `attribute-service` tự tăng counter và tự đối chiếu với threshold nội tại.
- CHỈ KHI chạm ngưỡng 10, `attribute-service` mới sinh ra 1 event `View_10_Items_Reward` đẩy vào `events-topic`. Lúc này TP mới nhảy vào cuộc để cộng điểm.
**Q2: Reset counter khi nào? Ai chịu trách nhiệm?**
- **Phương án chốt:** Hoạt động theo mô hình tự trị.
- **Tự reset khi Trigger:** Ngay khoảnh khắc `attribute-service` đẩy event `View_10_Items_Reward` đi, nó tự động reset counter của user đó về 0 (hoặc trừ đi 10). Không cần đợi TP xử lý xong để giảm giao tiếp vòng vèo (vì đây là event tracking, chấp nhận eventual consistency).
- **Reset theo chu kỳ (Time-based):** Đối với các counter có thời hạn (ví dụ: đếm trong tuần), hệ thống cần cơ chế Cronjob (từ `marketing-service` hoặc nội tại `attribute-service`) quét và reset toàn bộ counter về 0 vào lúc 00:00 thứ Hai hàng tuần.
### Vấn đề 3: Dynamic Attribute cho Khảo sát (UC-08)
- **Phương án điều chỉnh (Event-Driven hoàn toàn):**
- Trạng thái "đã làm khảo sát" được lưu động bằng EAV trong `attribute-service`.
- Không cần gọi API đồng bộ. Khi user hoàn thành, `member-app` chỉ cần bắn event `Survey_Completed` (loại `COUNTER`) về `event-processor-service`.
- Event Processor sẽ tự động route song song:
1. Đẩy vào `events-topic`: `transaction-service` hứng để tặng voucher.
2. Đẩy vào `tracking-topic`: `attribute-service` hứng và thực thi lệnh `SET_TRUE_ONCE` để update attribute `survey_completed = true`.
### Vấn đề 4: Time-based Triggers & Cross-System (UC-11, UC-13, UC-10)
- **UC-11 & UC-13 (Retention):** `marketing-service` chạy Cronjob quét database (PostgreSQL). Tìm ra danh sách User thoả mãn -> Bắn event vào `events-topic`. `transaction-service` hứng và cấp ưu đãi, sau đó đẩy thông báo sang `noti-topic`.
- **UC-10 (Merchant Scan Voucher):** `merchant-app` quét mã -> gọi API của `transaction-service`. `transaction-service` xử lý trừ voucher trong DB (đảm bảo ACID). Thành công -> bắn event `Voucher_Redeemed` vào `noti-topic` -> `notification-service` hứng và gọi Firebase push realtime hiển thị thông báo lên `member-app`.
---
## 4. Tóm lược Vai trò của Event Processor
Dưới góc nhìn kiến trúc mới nhất, `event-processor-service` là một **Smart Event Gateway**:
@@ -138,11 +41,11 @@ Dưới góc nhìn kiến trúc mới nhất, `event-processor-service` là mộ
1. **Ingest & Validate:** Hứng mọi loại event từ Client (member-app, hệ thống ngoài). Đảm bảo schema của event đúng định dạng chuẩn.
2. **Classify (Phân loại):** Xác định event thuộc loại nào:
- **Direct Event** → Cần xử lý logic/tài chính ngay.
- **Counter Event** → Cần tích lũy trước khi trigger.
- **Tracking Event** → Cần tích lũy trước khi trigger.
- **Notification Event** → Cần gửi thông báo trực tiếp.
3. **Routing (Định tuyến):**
- Direct Events (Redeem, Transfer, Profile_Completed...) → `events-topic``transaction-service`.
- Counter Events (Login, ViewItem, RedeemItem count...) → `tracking-topic` → `attribute-service` (tích lũy, khi đạt threshold → trigger `events-topic`).
- Tracking Events (Login, ViewItem, RedeemItem count...) → `tracking-topic``attribute-service` (tích lũy, khi đạt threshold → trigger `events-topic`).
- Notification Events → `noti-topic``notification-service`.
```
@@ -180,62 +83,6 @@ Dưới góc nhìn kiến trúc mới nhất, `event-processor-service` là mộ
---
## 5. Các Câu hỏi Cần Bàn bạc với Team
> Các phương án ở trên dựa trên giả định ban đầu trong quá trình phân tích. Dưới đây là tổng hợp tất cả các quyết định kiến trúc cần team xác nhận hoặc thảo luận thêm.
### 5.1. Kiến trúc Tổng quan
| # | Câu hỏi | Giả định hiện tại | Cần xác nhận |
|---|---------|-------------------|-------------|
| A1 | **Fat Event vs Thin Event:** Event gửi vào hệ thống nên chứa sẵn context (Fat) hay chỉ chứa ID (Thin) rồi service tự tra cứu? | **Hybrid:** Profile API bắn Fat Event (`Profile_Completed`), còn Login là Thin Event (Event Processor/TP tự check). | Team có đồng ý mô hình hybrid này không? Có event nào khác cần là Fat Event? |
| A2 | **Vai trò của `event-processor-service`:** Nó chỉ là Gateway/Router hay cần tham gia xử lý logic? | Chỉ làm **Smart Gateway**: Ingest → Classify → Route. Không chứa business logic. | Có trường hợp nào Event Processor cần tự xử lý logic trước khi route không? |
| A3 | **Phân tách Kafka Topics:** Hiện tại có `events-topic` và `noti-topic`. Có cần thêm `tracking-topic` cho counter events không? | **Cần thêm** `tracking-topic` để tách luồng tracking/counter khỏi luồng giao dịch tài chính. | Team đồng ý tách thêm topic? Hay dùng cách khác (VD: header-based routing trên cùng 1 topic)? |
### 5.2. Xử lý Login & Trạng thái Khách hàng (UC-01, UC-02, UC-05)
| # | Câu hỏi | Giả định hiện tại | Cần xác nhận |
|---|---------|-------------------|-------------|
| B1 | **First Login Event (UC-01):** Dùng `Login` chung với chuỗi đăng nhập. | **Dùng `SET_TRUE_ONCE` để bắn trigger event `First_Login_Reward`.** | Cần đảm bảo UI Client xử lý popup thưởng 100 điểm realtime khi nhận Noti trả về. |
| B2 | **Phát hiện "Inactive" (UC-05) như thế nào?** Bao nhiêu ngày không đăng nhập? | **Sử dụng phép toán `CHECK_AND_UPDATE_TIMER` tại Event Processor.** Khi Login, EP đóng gói lệnh check `last_login_date` gửi sang Attribute Service tính toán khoảng thời gian (Gap). | Chốt lại ngưỡng bao nhiêu ngày thì gửi Noti Welcome Back (VD: 90 ngày)? Có cần cấu hình ngưỡng này khác nhau cho từng hạng thẻ không? |
| B3 | **Reset Counter Login (UC-02):** Khi user lỡ 1 ngày, streak bị reset về 0. | **`attribute-service` tự so sánh `last_login_date` để reset streak.** | Logic "ngày" tính theo múi giờ nào (VD: UTC+7)? Hệ thống có ghi nhận time zone của user không? |
| B4 | **Giới hạn 1k điểm/năm (UC-02):** Tính theo năm dương lịch hay 365 ngày kể từ lần thưởng đầu tiên? | **Năm dương lịch** (01/01 - 31/12). | Xác nhận lại logic tính năm. |
### 5.3. Counter Events & Tracking (UC-03, UC-12)
| # | Câu hỏi | Giả định hiện tại | Cần xác nhận |
|---|---------|-------------------|-------------|
| C1 | **Pattern "Accumulate & Trigger":** Counter events (ViewItem) được tích lũy ở `attribute-service` bằng Dynamic Attribute. Khi đạt threshold thì mới trigger sang `events-topic`. | **`attribute-service` đảm nhận việc đếm và check threshold.** | `attribute-service` hiện có khả năng consume Kafka topic không? Hay cần build thêm consumer module? |
| C2 | **Cấu hình threshold nằm ở đâu?** | **Đề xuất: Gắn vào metadata của Dynamic Attribute** (VD: attribute `weekly_view_count` có field `threshold: 10`). Attribute-service self-contained. | Hay nên để `transaction-service` (Rule Engine) quản lý threshold vì đó là business rule? |
| C3 | **Reset counter khi nào?** | **Kết hợp:** (1) Reset sau khi thưởng (TP bắn `Counter_Reset_Command`), (2) Reset theo chu kỳ bởi `marketing-service` Cronjob (dọn dẹp). | Chu kỳ reset cụ thể là gì (weekly/monthly)? Có counter nào không cần reset (UC-12: tổng lượt redeem)? |
| C4 | **UC-03: Dữ liệu "Recently Viewed" (Recommend for You):** Lưu ở đâu để query nhanh cho trang chủ? | **Dynamic Attribute trong PostgreSQL**, có thể dùng Redis làm cache. | Có yêu cầu về thời gian phản hồi (latency) cho phần Recommend? Có cần real-time hay batch update là đủ? |
### 5.4. Luồng Giao dịch & Orchestration (UC-04, UC-06, UC-09, UC-10)
| # | Câu hỏi | Giả định hiện tại | Cần xác nhận |
|---|---------|-------------------|-------------|
| D1 | **UC-09 (Redeem):** Event Processor chỉ route, `transaction-service` hoàn tất giao dịch (trừ điểm, chốt kho), `notification-service` gửi noti. | **Event Processor KHÔNG tham gia xử lý giao dịch.** | Luồng Redeem hiện tại đã hoạt động theo mô hình này chưa, hay đang đi thẳng API? |
| D2 | **UC-10 (Merchant Scan Voucher):** `merchant-app` gọi thẳng API `transaction-service`, không đi qua Event Processor. | **Luồng đồng bộ (sync)** vì cần response ngay cho Merchant. | Merchant app gọi API nào hiện tại? Có cần bổ sung API mới không? |
| D3 | **UC-04 (Transfer Balance):** Chuyển điểm giữa 2 tài khoản — giao dịch kép (debit + credit). | **`transaction-service` xử lý cả 2 vế trong 1 transaction ACID.** | Đã có API transfer chưa? Có giới hạn số điểm chuyển không? |
### 5.5. Scheduled Jobs & Retention (UC-07, UC-08, UC-11, UC-13)
| # | Câu hỏi | Giả định hiện tại | Cần xác nhận |
|---|---------|-------------------|-------------|
| E1 | **UC-07 (Update Profile):** Logic "đầy đủ thông tin" do ai check? | **Profile API tự check rồi emit `Profile_Completed_Event`.** | Profile API hiện có logic check completeness chưa? Danh sách field bắt buộc gồm những gì? |
| E2 | **UC-07 (Update Profile):** "Trước ngày 30/09" — đây là ngày cố định hay cấu hình được? | **Cố định theo campaign.** | Nếu campaign thay đổi mốc thời gian thì cập nhật ở đâu? |
| E3 | **UC-08 (Survey):** Trạng thái "đã làm khảo sát" lưu bằng Dynamic Attribute trong `attribute-service`. | **Tạo attribute `survey_completed` (BOOLEAN) cho entity Customer.** | Nếu có nhiều survey khác nhau thì sao? Cần 1 attribute per survey hay 1 attribute chung? |
| E4 | **UC-11 (Password Expiring):** `marketing-service` quét tập khách hàng có mật khẩu sắp hết hạn. | **`marketing-service` chạy Cronjob.** | Thông tin "ngày hết hạn mật khẩu" nằm ở DB nào? `marketing-service` có quyền truy cập DB đó không? Hay cần gọi API từ IAM? |
| E5 | **UC-13 (Dormant 3 tháng):** Xác định txn cuối cùng, không tính txn auto (phát sinh bởi OLS). | **`marketing-service` query bảng transaction, filter loại trừ txn auto.** | Txn auto có flag riêng để phân biệt không? Query có nặng không nếu bảng transaction lớn? |
### 5.6. Notification & Push Realtime (UC-10)
| # | Câu hỏi | Giả định hiện tại | Cần xác nhận |
|---|---------|-------------------|-------------|
| F1 | **Push realtime lên Portal:** Khi Merchant quét voucher, Portal khách hàng nhận thông báo ngay lập tức. | **Dùng Firebase Cloud Messaging (đã có sẵn).** | Firebase hiện push tới `member-app` (mobile) hay cả web portal? Cần kiểm tra scope tích hợp. |
---
## 6. Sơ đồ Kiến trúc Tổng thể & Giải pháp Chi tiết từng Use Case
### 6.1. Architecture Diagram — Tổng thể hệ thống
@@ -275,7 +122,7 @@ graph TB
%% Event Processor → Kafka
EP -->|"Direct Events"| ET
EP -->|"Counter Events"| TT
EP -->|"Tracking Events"| TT
EP -->|"Notification Events"| NT
%% Kafka → Consumers
@@ -360,7 +207,7 @@ sequenceDiagram
participant NS as notification-service
MA->>EP: Login Event
EP->>EP: Classify: Counter Event
EP->>EP: Classify: Tracking Event
EP->>TT: Publish Login
TT->>ATTR: Consume Login Event
@@ -420,7 +267,7 @@ sequenceDiagram
participant TP as transaction-service
MA->>EP: Login Event
EP->>EP: Classify: Counter Event
EP->>EP: Classify: Tracking Event
EP->>TT: Publish Login
TT->>ATTR: Consume Login Event
@@ -465,7 +312,7 @@ sequenceDiagram
participant REDIS as Redis (Cache)
MA->>EP: ViewItem Event<br/>{itemId, customerId}
EP->>EP: Classify: Counter Event
EP->>EP: Classify: Tracking Event
EP->>KT: Publish to tracking-topic
KT->>ATTR: Consume ViewItem
@@ -485,7 +332,7 @@ sequenceDiagram
```
**Giải pháp:**
- **Classification:** `event-processor-service` phân loại `ViewItem` là Counter Event → đẩy `tracking-topic`.
- **Classification:** `event-processor-service` phân loại `ViewItem`Tracking Event → đẩy `tracking-topic`.
- **attribute-service** xử lý 3 việc:
1. Tăng `item_view_count` (Dynamic Attribute trên entity **Item**) — phục vụ ranking/trending.
2. Cập nhật `recently_viewed` (Dynamic Attribute trên entity **Customer**) — danh sách item gần đây. Tính năng này sử dụng phép toán `APPEND_UNIQUE` lấy từ `event.itemId`, kết hợp cơ chế FIFO với giới hạn `maxLength` (VD: 50 items) để chống phình to dữ liệu.
@@ -1020,48 +867,4 @@ Khi đạt Threshold, Attribute Service đóng gói một Event hợp lệ (Dire
**Transaction Service (TP)**:
Lắng nghe `events-topic`, bắt được event `Login_Streak_7_Days`, lục tìm Rule Engine và thấy quy định: *"Tặng 100 điểm"*. Tiến hành trừ kho và cộng điểm, kết thúc flow.
---
## 8. Phân tích Edge Cases & Giải pháp Kỹ thuật (Mitigations)
Để đảm bảo hệ thống an toàn ở quy mô lớn, chúng ta cần xử lý triệt để các Edge Cases tiềm ẩn (đặc biệt là các lỗi liên quan đến đồng thời - Concurrency và Idempotency). Dưới đây là 2 Edge Cases nguy hiểm nhất và giải pháp thiết kế tương ứng.
### 8.1. Vấn đề "Threshold Jumping" (Cộng dồn vượt ngưỡng)
**Ngữ cảnh:**
Thuộc tính `weekly_view_count` đang có giá trị là 9. Khách hàng sử dụng tool gửi liên tục 5 request `ViewItem` vào cùng 1 tích tắc. Các Kafka consumer xử lý 5 event này gần như đồng thời.
**Rủi ro:**
Nếu không kiểm soát đồng thời (Concurrency Control), giá trị có thể được tăng lên 14. Nếu logic trigger chỉ đơn thuần là `if (new_value >= threshold)` (14 >= 10), hệ thống sẽ lọt vào nhánh kích hoạt (Trigger) tới 5 lần, dẫn đến việc tặng điểm 5 lần cho 1 mốc.
**Giải pháp (Boundary Check & DB Row Lock):**
1. **Pessimistic Locking (Khóa bi quan):** Tại `attribute-service`, mọi hành động cập nhật `customer_attribute_values` đều phải sử dụng khóa dòng (Row-level lock) trong PostgreSQL (`SELECT ... FOR UPDATE`). Điều này buộc 5 request đồng thời phải xếp hàng chạy tuần tự.
2. **Boundary Condition (Điều kiện ranh giới):** Logic trigger phải được cập nhật thành:
`if (old_value < threshold AND new_value >= threshold) THEN trigger()`
3. **Luồng thực thi an toàn:**
- **Req 1:** old=9, new=10 -> TRÚNG ĐIỀU KIỆN (9 < 10 và 10 >= 10) -> Bắn Trigger -> Reset value về 0.
- **Req 2:** old=0, new=1 -> TRƯỢT.
- Các Req 3, 4, 5 tương tự sẽ làm value tăng lên 2, 3, 4 mà không bao giờ kích hoạt lại Threshold.
**Rủi ro DB Contention (Đặc biệt lưu ý):**
Dù Kafka đã đứng làm buffer (không làm sập upstream), nhưng nếu có hàng ngàn event `ViewItem` cho **cùng một sản phẩm HOT** đẩy vào, các Kafka Consumer Threads sẽ cùng lúc thực thi `SELECT ... FOR UPDATE` lên đúng 1 dòng (row) của entity `ITEM` trong DB. Điều này gây ra **Database Contention (Tắc nghẽn DB)** giữa các thread xử lý, giới hạn thông lượng (throughput) và có thể gây lock timeout hoặc thread starvation.
*Cách xử lý:* Cần phân loại Counter:
1. **Entity-Level Counter (Customer):** VD `weekly_view_count`, `login_streak`. Mức độ đồng thời trên 1 khách hàng là cực kỳ thấp -> **Dùng Row-level Lock (Pessimistic Locking)** là hoàn toàn phù hợp và an toàn tuyệt đối.
2. **Global/Hot Item Counter:** VD `item_view_count`, `total_redeem_count`. Mức độ đồng thời trên 1 row cực kỳ cao. -> **Không nên lock trực tiếp vào DB**. Hãy chuyển các counter thuần tracking này lên **Redis (Redis INCR)** để đếm trong RAM, sau đó cấu hình 1 Scheduler (VD: chạy mỗi 10 giây) để Flush (Batch Update) giá trị từ Redis xuống PostgreSQL.
### 8.2. "Bóng ma" HTTP Timeout tại Merchant App (UC-10)
**Ngữ cảnh:**
Merchant quét Voucher, App gọi Sync API POST lên `transaction-service`.
TP xử lý trừ Voucher thành công (đã commit DB), nhưng quá trình trả Response (HTTP 200 OK) về cho App bị gián đoạn do rớt mạng (Network Timeout).
**Rủi ro:**
Merchant App nhận lỗi Timeout, Merchant tưởng chưa thành công nên bấm quét lại. TP nhận request thứ 2, thấy Voucher đã ở trạng thái USED nên báo lỗi "Voucher đã được sử dụng". Merchant không chịu giao hàng cho khách.
**Giải pháp (Idempotency Key):**
Thiết kế Idempotent API cho mọi endpoint đồng bộ (Sync API).
1. **Client Generation:** Merchant App tự sinh ra một UUID (VD: `Idempotency-Key: req-556677`) và đính kèm vào Header của Request đầu tiên.
2. **TP Caching / DB Table:** `transaction-service` lưu một bảng `idempotent_requests(key, status, response_body)`.
- Lần gọi 1: TP tra bảng thấy key chưa tồn tại -> Xử lý giao dịch -> Commit -> Lưu `status=SUCCESS`, `response_body={"msg": "Thành công"}` vào bảng. Do rớt mạng, App không nhận được.
- Lần gọi 2 (Retry): App gửi nguyên lại `Idempotency-Key: req-556677`.
- TP tra bảng, thấy key đã tồn tại và `status=SUCCESS`. Nó **bỏ qua giao dịch** và chỉ trả về nguyên vẹn cái `response_body` cũ (HTTP 200 OK).
3. **Kết quả:** Merchant App nhận được phản hồi thành công như chưa từng có lỗi mạng xảy ra. Không hề có lỗi "Voucher đã sử dụng".
---