39 KiB
Event Processor Service — High-Level Design
Hệ thống Event-Driven Loyalty & Rewards
1. Tổng quan (System Overview)
1.1. Mục tiêu
- Chuẩn hóa xử lý Event: Tạo ra một flow chung duy nhất cho mọi luồng sự kiện từ các Client/App gửi về hệ thống.
- Giảm tải Transaction Service (TP): Bảo vệ core engine (TP) khỏi hàng triệu sự kiện "rác" (như Login, View, Click). TP chỉ thức dậy khi có giao dịch tài chính hoặc cần phát thưởng thực sự.
- Cho phép Marketing cấu hình Campaign linh hoạt: Cung cấp bộ công cụ (thông qua Attribute/Event Processor) để cấu hình các ngưỡng thưởng (threshold) đa dạng mà không cần Dev phải sửa code.
- Đảm bảo giao dịch tài chính ACID: Các giao dịch cộng/trừ điểm, phát voucher, trừ kho vẫn được xử lý tập trung tại Transaction Service với độ an toàn dữ liệu cao nhất.
1.2. Nguyên tắc thiết kế
- Transaction Service CHỈ xử lý các nghiệp vụ tài chính, Rule Engine (cộng/trừ điểm, tặng voucher, giới hạn thưởng). Không giữ các bộ đếm rác.
- Attribute Service (Dumb Executor) CHỈ làm nhiệm vụ lưu trữ trạng thái (state), tính toán các phép toán cơ bản (cộng dồn, đếm ngày, kiểm tra khoảng thời gian) dựa trên chỉ thị. Hoàn toàn KHÔNG biết về business logic (như thưởng bao nhiêu, rule gì).
- Event Processor (Smart Gateway) CHỈ làm nhiệm vụ Validate (kiểm tra định dạng) và Routing (phân loại & điều phối luồng sự kiện). Đóng vai trò là nơi lưu trữ cấu hình (Event Registry).
- Marketing Service CHỈ làm nhiệm vụ lập lịch (Scheduler) hoặc định kỳ quét tập khách hàng để phát sinh Event đầu vào. KHÔNG trực tiếp thực hiện cộng điểm hay xử lý tài chính.
1.3. Đối tượng & Hệ thống liên quan
- Actors (Người dùng): Khách hàng (Member) sử dụng ứng dụng di động/web; Nhân viên đối tác (Merchant) sử dụng ứng dụng quét mã.
- External Systems (Hệ thống ngoài): Hệ thống phát hành thẻ (Card Issuance), Hệ thống gửi tin (SMS/Email/Firebase).
2. Kiến trúc & Topology
2.1. Sơ đồ kiến trúc tổng quan
Hệ thống được chia thành các phân lớp (layers) rõ ràng nhằm đảm bảo tính độc lập, dễ bảo trì và dễ dàng mở rộng.
graph TD
subgraph Client Layer
MA[Member App]
MRA[Merchant App]
end
subgraph Event Gateway Layer
EP[event-processor-service<br/>Smart Router]
end
subgraph Message Broker Layer
K_ET[Kafka: events-topic]
K_TT[Kafka: tracking-topic]
K_NT[Kafka: noti-topic]
end
subgraph Core Service Layer
TP[transaction-service<br/>Core Rule & Ledger]
ATTR[attribute-service<br/>Tracking & Counter]
NS[notification-service<br/>Push/SMS]
MKT[marketing-service<br/>Scheduler]
end
subgraph Data & Caching Layer
DB_TP[(PostgreSQL<br/>Transaction)]
DB_ATTR[(PostgreSQL<br/>EAV/Attribute)]
REDIS[(Redis<br/>Cache)]
end
MA -->|Events: Login, View, Redeem| EP
MRA -.->|Sync API: Scan Voucher| TP
EP -->|Direct Events| K_ET
EP -->|Tracking Events| K_TT
EP -->|Noti Events| K_NT
K_ET --> TP
K_TT --> ATTR
K_NT --> NS
MKT -->|Cron: Scheduled Events| EP
ATTR -->|Threshold Reached Trigger| K_ET
TP -->|Reward Result| K_NT
TP --- DB_TP
ATTR --- DB_ATTR
ATTR -.-> REDIS
2.2. Phân loại Event
Mọi sự kiện đầu vào được chia làm 3 loại duy nhất:
- Direct Event: Cần xử lý logic/tài chính ngay lập tức (VD: Redeem Item, Transfer Balance, Welcome Offer). Sẽ được đẩy thẳng cho Transaction Service.
- Tracking Event: Cần tích lũy, đếm hoặc kiểm tra điều kiện trước khi trigger (VD: Daily Login, View Item). Sẽ được đẩy cho Attribute Service để lưu trữ và tính toán.
- Notification Event: Các sự kiện chỉ mang mục đích gửi thông báo, không có logic tài chính hay đếm (VD: Cảnh báo mật khẩu sắp hết hạn). Sẽ được đẩy thẳng cho Notification Service.
2.3. Vai trò các Service
- Event Processor:
Validate + Route. Nơi nhận Thin Event, tra cứu cấu hình, biến thành Fat Payload và định tuyến vào đúng Topic Kafka. - Attribute:
Tracking / State. Cỗ máy tính toán cơ bắp. Nhận Fat Payload, thực thi các phép toán (+, -, gán cờ) vào DB. Sinh sự kiện mới nếu đạt threshold. - Transaction:
Rule Engine + Reward. Đảm bảo tính ACID của giao dịch. Cộng điểm, trừ điểm, trả voucher theo đúng Rule được định cấu hình. - Notification:
Push. Gửi SMS, Email, Firebase Push tới End-User. - Marketing:
Scheduler. Quét tập dữ liệu định kỳ (cronjob) để sinh ra sự kiện tự động (như User ngủ đông, sắp sinh nhật, mật khẩu hết hạn).
2.4. Technology Stack
| Thành phần | Công nghệ | Lý do |
|---|---|---|
| Backend | Java / Spring Boot | Hệ sinh thái phong phú, độ ổn định cực cao (Enterprise-grade) chuyên trị các hệ thống tài chính/giao dịch khắt khe |
| Message Broker | Apache Kafka | Khả năng xử lý thông lượng khổng lồ (high-throughput), độ trễ cực thấp, persistence và Consumer Groups |
| Database | PostgreSQL | Hỗ trợ ACID mạnh mẽ cho Transaction Service, đồng thời hỗ trợ JSONB cho mô hình EAV của Attribute Service |
| Caching | Redis | Tốc độ Read/Write in-memory cực nhanh, lưu trữ cache phi tài chính (recently viewed items) |
| Data Warehouse | Doris | Phục vụ báo cáo, phân tích (BI) và phân tập khách hàng (Segmentation) mà không ảnh hưởng OLTP |
3. Luồng xử lý (Workflows)
3.1. Tracking Flow — "Accumulate & Trigger" (Ví dụ: View Item)
Mô tả: Nhằm tránh làm nghẽn Core Engine bởi các sự kiện có tần suất cao, hệ thống sử dụng Attribute Service để đếm (Counter). Chỉ khi đạt ngưỡng quy định (Threshold), một sự kiện trả thưởng mới được sinh ra và đẩy cho Core Engine xử lý.
sequenceDiagram
participant MA as Member App
participant EP as Event Processor
participant K_TT as Kafka (tracking-topic)
participant ATTR as Attribute Service
participant K_ET as Kafka (events-topic)
participant TP as Transaction Service
MA->>EP: Gửi Event (VD: ViewItem)
EP->>EP: Phân loại: Tracking Event
EP->>K_TT: Đẩy vào tracking-topic
K_TT->>ATTR: Consume Event
ATTR->>ATTR: 1. Cập nhật Counter (weekly_view_count++)<br/>2. Cập nhật Cache (recently_viewed)
alt Counter >= Threshold (VD: 10)
ATTR->>K_ET: Bắn Event Trả Thưởng (View_10_Items_Reward)
ATTR->>ATTR: Reset Counter
K_ET->>TP: Consume Reward Event
TP->>TP: Cộng điểm (Transaction ACID)
end
3.2. Direct Flow — Giao dịch Tài chính Trực tiếp (Ví dụ: Redeem Item / Transfer)
Mô tả: Các giao dịch mang tính tài chính trực tiếp (chuyển điểm, đổi quà) được gọi trực tiếp qua API tới Transaction Service để xử lý đồng bộ (Synchronous), giúp đảm bảo tốc độ phản hồi ngay lập tức cho người dùng và tính toàn vẹn dữ liệu.
sequenceDiagram
participant MA as Member App
participant TP as Transaction Service
participant K_NT as Kafka (noti-topic)
participant NS as Notification Service
MA->>TP: Gọi API trực tiếp (Redeem_Item / Transfer)
TP->>TP: BEGIN DB TRANSACTION
TP->>TP: Validate Số dư & Điều kiện
TP->>TP: Debit (-) Điểm & Cập nhật State/Inventory
TP->>TP: COMMIT TRANSACTION
TP-->>MA: Trả kết quả thành công (200 OK)
TP->>K_NT: Bắn Event Giao dịch Thành công
K_NT->>NS: Consume Event
NS->>MA: Đẩy Noti (Firebase Push/SMS)
3.3. Payload Transformation
Sự biến đổi của dữ liệu từ Client cho tới lúc phát thưởng:
- Thin Event (Từ Client): Rất mỏng, chỉ chứa
{ event: "Login", customerId: "CUST-001", eventTime: "2026-07-16T10:30:00+07:00" }. Không chứa rule hay logic. - Fat Payload (Từ Event Processor gửi Kafka): Chứa Thin Event + Kẹp thêm mảng các
Instructions(lấy từ Registry). Để chỉ đạo Attribute Service phải tính toán cái gì. - Trigger Event (Do Attribute publish vào events-topic): Khi đạt Threshold, sinh ra một sự kiện rõ ràng. VD:
{ event: "Login_Streak_7_Days", customerId: "CUST-001" }. Transaction Service chỉ cần đọc tên event này từ Kafka để trả thưởng.
4. Use Case Mapping
4.1. Use Case Template
Để thống nhất ngôn ngữ giao tiếp, mọi Use Case mới sẽ được mô tả theo Template chuẩn sau:
- Business Requirement: Yêu cầu nghiệp vụ (Mô tả ngắn gọn mục đích).
- Event: Tên sự kiện gốc từ Client gửi lên (VD:
Login,ViewItem). - Event Type: Phân loại (
Direct,Tracking,Notification). - Processing Flow: Luồng xử lý (
Direct FlowhoặcTracking Flow). - Attributes: Các thuộc tính cần cập nhật lưu tại Attribute Service (VD:
login_streak,weekly_view_count). Nếu là Direct Flow thì để trống. - Trigger: Tên sự kiện sinh ra khi đủ điều kiện để báo cho Transaction Service (VD:
Login_Streak_7_Days). - Output: Kết quả cuối cùng (VD: +100 điểm, tặng 1 voucher).
4.2. Bảng Ánh xạ Nghiệp vụ - Kỹ thuật (UC-to-Solution Mapping)
| Mã UC / Tên Use Case | Giải pháp Kỹ thuật tương ứng | Thành phần liên quan (Components) |
|---|---|---|
| UC-01 - First Login (Đăng nhập lần đầu) |
Gán cờ first_login_flag bằng lệnh SET_TRUE_ONCE. Thành công sẽ kích hoạt event First_Login_Reward để hệ thống cộng điểm và đẩy thông báo realtime. |
attribute-service, transaction-service, Kafka (tracking-topic, events-topic), PostgreSQL (Attr DB) |
| UC-02 - Daily Login Tracking (Điểm danh hàng ngày) |
Cập nhật bộ đếm current_streak và last_login_date. Bỏ qua các sự kiện trùng lặp trong ngày (Idempotency). Khi đủ 7/30 ngày, trigger event trả thưởng. |
attribute-service, transaction-service, Kafka (tracking-topic, events-topic) |
| UC-03 - View Item (Xem chi tiết quà) |
Cập nhật item_view_count, lưu lịch sử recently_viewed vào Cache. Cộng weekly_view_count, đủ 10 views sẽ trigger event cộng điểm. |
attribute-service, Redis (Cache), Kafka (tracking-topic), PostgreSQL (Attr DB) |
| UC-04 - Transfer Balance (Chuyển điểm) |
Xử lý giao dịch trừ tiền người gửi, cộng tiền người nhận trong cùng 1 ACID Transaction. Bắn push notification cho cả hai bên. | transaction-service, notification-service, Kafka (events-topic), PostgreSQL (Txn DB). Đã có luồng xử lý, không cần đi qua events |
| UC-05 - Inactive Account Login (Login sau thời gian ngủ đông) |
Kèm cấu hình CHECK_AND_UPDATE_TIMER vào event Login. Nếu GAP >= 90 ngày, trigger event Account_Reactivated để đổi trạng thái và gửi noti Welcome Back. |
attribute-service, transaction-service, Kafka (tracking-topic, events-topic) |
| UC-06 - Welcome Offer (Mở thẻ mới) |
Hệ thống phát hành thẻ (External) bắn event. transaction-service kiểm tra khóa bảo vệ (Idempotency) để đảm bảo chỉ thưởng 1 lần. |
Hệ thống phát hành thẻ, transaction-service, Kafka (events-topic) |
| UC-07 - Update Profile (Hoàn tất hồ sơ) |
profile-service tự đánh giá độ hoàn thiện và bắn event. Core engine kiểm tra mốc thời gian (trước 30/09) để quyết định cộng điểm. |
profile-service, transaction-service, Kafka (events-topic) |
| UC-08 - Survey (Hoàn thành khảo sát) |
Bắn 1 event nhưng định tuyến song song 2 luồng: (1) Cấp voucher và (2) Cập nhật cờ survey_completed = true để không hiển thị lại popup. |
transaction-service, attribute-service, Kafka (events-topic, tracking-topic) |
| UC-09 - Redeem Item (Đổi quà) |
Kiểm tra số dư, trừ điểm và khóa tồn kho (reserve item) trong 1 giao dịch cơ sở dữ liệu nguyên tử (ACID DB Transaction). | transaction-service, notification-service, PostgreSQL (Txn DB). Đã có luồng xử lý, không cần đi qua events |
| UC-10 - Merchant Scan (Quét mã voucher) |
API Đồng bộ (Synchronous). Đổi trạng thái voucher sang USED (ACID). Thành công sẽ đẩy noti realtime cho người dùng qua Kafka. | Merchant App, transaction-service, API Gateway, PostgreSQL (Txn DB). Đã có luồng xử lý, không cần đi qua events |
| UC-11 - Retention (Mật khẩu sắp hết hạn) |
Scheduler chạy hằng ngày quét DB lọc danh sách password_expiry_date. Bắn thẳng thông báo vào Kafka mà không cần đi qua Core engine. |
marketing-service, notification-service, Kafka (noti-topic). Đã có luồng xử lý, không cần đi qua events |
| UC-12 - Most Redeem Item (Sản phẩm đổi nhiều nhất) |
Event Processor sao chép luồng sự kiện đổi quà sang tracking-topic để cập nhật counter total_redeem_count trên thuộc tính sản phẩm (Thuần Tracking). |
Event Processor, attribute-service, Kafka (tracking-topic), PostgreSQL (Attr DB) |
| UC-13 - Retention (User Dormant 3 tháng) |
Scheduler chạy hằng ngày lọc user có last_user_txn_date >= 90 ngày. Bắn event vào Core engine để thực thi cấp ưu đãi riêng và push noti. |
marketing-service, transaction-service, PostgreSQL (Txn DB & Marketing DB). Đã có luồng xử lý, không cần đi qua events |
4.3. Phân nhóm giải pháp kỹ thuật
Nhóm 1: Hệ thống đếm và Theo dõi (Tracking & Counters)
Bao gồm: UC-01, UC-02, UC-03, UC-05, UC-12
- Luồng dữ liệu: Client ->
event-processor-service->tracking-topic->attribute-service. - Pattern "Accumulate & Trigger":
attribute-servicehoạt động như một "Dumb Executor" tích lũy các event có tần suất cao (như Login, ViewItem). Khi đạt ngưỡng (Threshold) hoặc điều kiện trạng thái, nó mới sản sinh một event "có giá trị tài chính" đẩy vàoevents-topicđểtransaction-servicetrả thưởng. - Caching: Dùng Redis để lưu trữ thuộc tính
recently_viewed(UC-03) — độ trễ <10ms. - Idempotency: Bộ đếm tự động lọc bỏ các hành động lặp lại không hợp lệ, ví dụ login nhiều lần trong 1 ngày (UC-02) hoặc kiểm tra cờ (UC-01).
Nhóm 2: Giao dịch Tài chính & Quản lý Kho (Financial & Inventory)
Bao gồm: UC-04, UC-09, UC-10
- Luồng dữ liệu:
- (UC-04, UC-09): Client ->
event-processor-service->events-topic->transaction-service. - (UC-10): Merchant App -> HTTP Sync API ->
transaction-service.
- (UC-04, UC-09): Client ->
- ACID Transactions / Rollback: Việc trừ điểm người gửi & cộng điểm người nhận (UC-04) hoặc trừ điểm & khóa tồn kho (UC-09) bắt buộc nằm chung trong 1 Database Transaction nguyên tử.
- Sync API: UC-10 thực thi trực tiếp qua Restful API để trả về kết quả ngay lập tức trên máy POS của Merchant.
Nhóm 3: Tương tác Hệ thống Ngoài & Luồng Song song
Bao gồm: UC-06, UC-07, UC-08
- Luồng dữ liệu: External Systems / App ->
event-processor-service->events-topic->transaction-service. - Dual Publishing: Trong UC-08, Event Processor sao chép sự kiện thành 2 luồng:
events-topic(lấy voucher) +tracking-topic(ẩn màn hình khảo sát). Giảm liên kết cứng (decouple) giữa UI State và Financial Ledger.
Nhóm 4: Cronjob, Batch Processing & Retention
Bao gồm: UC-11, UC-13
- Luồng dữ liệu:
marketing-service(Cronjob) -> Query Database -> Kafka -> Các Service liên quan. - Off-peak Processing: Chạy các job quét CSDL vào giờ thấp điểm (đêm khuya).
- Phân luồng: UC-11 chỉ gửi tin nhắn →
noti-topic. UC-13 liên quan đến tặng quà →events-topic.
5. Attribute Service — Operation Catalog
Attribute Service hỗ trợ một tập cố định các Primitive Operations (Toán tử nguyên thủy). Marketing/BA có thể lắp ghép các toán tử này để tạo ra vô số Campaign mà không cần Dev sửa code.
5.1. Quy tắc chung
- Timezone: Mọi phép so sánh ngày/giờ đều được convert về
Asia/Ho_Chi_Minhtrước khi xử lý. - Time Source: Luôn dùng Event Time (thời điểm sự kiện xảy ra tại Client/Source, nằm trong payload) — KHÔNG dùng Processing Time (thời điểm Attribute Service nhận event). Điều này đảm bảo kết quả deterministic, không phụ thuộc vào tải hệ thống hay Kafka lag.
- Key chưa tồn tại: Khi nhận Instruction cho một Key chưa có trong DB, Attribute Service tự khởi tạo giá trị mặc định rồi thực thi Operation.
- Concurrency: Kafka topic
tracking-topicPHẢI partition theocustomerId. Mọi event của cùng một user luôn vào cùng một partition → được xử lý tuần tự bởi một consumer duy nhất → không xảy ra race condition. - Threshold Trigger: Mặc định
triggerOnce: true. Mỗi threshold chỉ fire đúng 1 lần khi value lần đầu thỏa mãn condition. Attribute Service tra cứu bảngthreshold_fire_log(theoattribute_key+threshold_id) để ngăn trigger lặp lại.
5.2. Attribute Record
Mỗi Key trong Attribute Service được lưu trữ với cấu trúc sau:
┌─ Attribute Record ──────────────────────────────────┐
│ key: "user:CUST-001:login_streak" │
│ value: 5 (Number/String/Bool) │
│ last_updated: "2026-07-15T23:50:00+07:00" │
│ created_at: "2026-07-10T08:00:00+07:00" │
└──────────────────────────────────────────────────────┘
last_updatedđược Attribute Service tự cập nhật (bằng Event Time) mỗi khi có write thành công. Đây là nền tảng để xử lý mọi bài toán thời gian.
5.2b. Threshold Fire Log (Bảng lịch sử kích hoạt Threshold)
Vì 1 attribute có thể phục vụ nhiều threshold khác nhau (ví dụ: login_streak có threshold == 7 để thưởng 100đ VÀ threshold == 30 để thưởng 500đ).
┌─ Threshold Fire Log ────────────────────────────────────────────────────┐
│ id: (auto) │
│ customer_id: "CUST-001" │
│ attribute_key: "login_streak" │
│ threshold_id: "Login_Streak_7_Days" │
│ event_id: "evt-2026-07-16-abc123" (Event gốc gây trigger) │
│ fired_at: "2026-07-16T10:30:00+07:00" (Event Time) │
│ created_at: "2026-07-16T10:30:05+07:00" (Processing Time) │
└─────────────────────────────────────────────────────────────────────────┘
| Field | Mô tả |
|---|---|
customer_id |
ID khách hàng sở hữu attribute |
attribute_key |
Key của attribute đã thỏa mãn điều kiện |
threshold_id |
Định danh threshold (trùng với triggerEvent trong Instruction). Dùng làm Unique Constraint cùng customer_id + attribute_key để đảm bảo triggerOnce |
event_id |
ID của event gốc (từ Client) đã gây ra trigger — phục vụ traceability và debug |
fired_at |
Thời điểm sự kiện xảy ra (Event Time) — dùng để audit |
Quy trình kiểm tra triggerOnce:
- Sau khi Operation thực thi xong và value thay đổi, evaluate threshold condition.
- Nếu thỏa mãn → Tra cứu
threshold_fire_logvới composite key(customer_id, attribute_key, threshold_id). - Nếu chưa có record → INSERT log + Publish Trigger Event.
- Nếu đã có record → Bỏ qua (threshold đã fire trước đó).
Ví dụ: login_streak có 2 thresholds
Threshold A: { "==": 7, triggerEvent: "Login_Streak_7_Days" }
Threshold B: { "==": 30, triggerEvent: "Login_Streak_30_Days" }
Ngày 7: login_streak = 7
→ Check A: 7 == 7 ✓, log chưa có → INSERT log + Trigger "Login_Streak_7_Days" ✓
→ Check B: 7 == 30 ✗ → Bỏ qua
Ngày 30: login_streak = 30
→ Check A: 30 == 7 ✗ → Bỏ qua
→ Check B: 30 == 30 ✓, log chưa có → INSERT log + Trigger "Login_Streak_30_Days" ✓
Ngày 31: login_streak = 31 (streak reset rồi đếm lại → giả sử đạt 30 lần nữa)
→ Check B: 30 == 30 ✓, log ĐÃ CÓ → Bỏ qua. Không thưởng lần 2.
5.3. Phase 1 — Operations cần thiết cho 13 Use Cases hiện tại
SET
Ghi đè giá trị vô điều kiện (Overwrite).
| Thuộc tính | Giá trị |
|---|---|
| Kiểu value | String, Number, Boolean, Date |
| Key chưa tồn tại | Tạo mới với value được truyền vào |
| Idempotent | Không. Mỗi lần gọi đều ghi đè |
Use Case: UC-05 — Mỗi lần User login, cập nhật SET last_login_date = eventTime. Kết hợp với Threshold TIME_GAP_GT để phát hiện khoảng cách ngủ đông.
Hành vi chi tiết:
- Nếu có threshold
TIME_GAP_GT: Kiểm tra threshold TRƯỚC khi ghi đè. So sánh(eventTime - old_value). Nếu thỏa mãn → sinh Trigger Event. Sau đó mới ghi đè value mới. - Nếu không có threshold: Ghi đè trực tiếp.
Ví dụ UC-05 (Login sau 90 ngày ngủ đông):
Trạng thái hiện tại: last_login_date = "2026-04-15"
Event đến: eventTime = "2026-07-16"
Bước 1: Check threshold TIME_GAP_GT 90 days
→ (2026-07-16) - (2026-04-15) = 92 ngày > 90 ✓
→ Publish Trigger Event: "Account_Reactivated"
Bước 2: SET last_login_date = "2026-07-16"
SET_TRUE_ONCE
Đánh dấu cờ Boolean = true. Chỉ có hiệu lực đúng 1 lần duy nhất khi key chưa tồn tại hoặc đang là false/null. Đảm bảo Idempotency (tính lũy đẳng) tuyệt đối.
| Thuộc tính | Giá trị |
|---|---|
| Kiểu value | Boolean (cố định) |
| Key chưa tồn tại | Tạo mới = true, sinh Trigger Event |
Key đã là true |
Bỏ qua hoàn toàn — không ghi, không trigger |
Use Cases: UC-01 (First Login), UC-08 (Survey Completed).
Hành vi chi tiết:
Ví dụ UC-01 (First Login):
Lần 1: key "first_login" chưa tồn tại
→ Tạo mới: value = true
→ Trigger Event: "First_Login" ✓
Lần 2: key "first_login" = true (đã tồn tại)
→ Bỏ qua. Không ghi, không trigger.
→ User KHÔNG nhận thưởng lần 2. An toàn.
INCREMENT
Cộng dồn giá trị số. Hỗ trợ 2 chế độ thời gian tùy chọn: Streak (chuỗi liên tiếp) và Window (cửa sổ chu kỳ).
| Thuộc tính | Giá trị |
|---|---|
| Kiểu value | Number |
| Key chưa tồn tại | Tạo mới = 0, rồi thực thi +val → kết quả = val |
| Tham số bắt buộc | val (số cần cộng, thường = 1) |
| Tham số tùy chọn | streak hoặc window (loại trừ nhau) |
Chế độ 1: Không có streak/window — Cộng dồn thuần túy, không reset.
Ví dụ UC-12 (Most Redeem — Bộ đếm tích lũy vĩnh viễn):
INCREMENT total_redeem_count, val: 1
Lần 1: 0 + 1 = 1
Lần 2: 1 + 1 = 2
...
Lần N: (N-1) + 1 = N
Không bao giờ reset. Dùng để thống kê, ranking.
Chế độ 2: streak — Đếm chuỗi liên tiếp.
Dùng để đếm số ngày/tuần/tháng liên tiếp mà user thực hiện hành vi. Nếu đứt chuỗi (bỏ qua 1 chu kỳ), reset về 1 và đếm lại.
Giá trị streak hợp lệ: DAILY, WEEKLY, MONTHLY.
Logic xử lý (dựa trên last_updated và eventTime, đã convert về Asia/Ho_Chi_Minh):
streak: "DAILY"
Tính khoảng cách ngày giữa eventTime và last_updated:
┌──────────────────────┬──────────────────────────────────────────┐
│ Khoảng cách │ Hành vi │
├──────────────────────┼──────────────────────────────────────────┤
│ Cùng ngày │ Bỏ qua (Idempotent — login 5 lần/ngày │
│ │ chỉ đếm 1) │
│ Đúng 1 ngày trước │ value = value + val (streak tiếp tục) │
│ Cách > 1 ngày │ value = 1 (gán thẳng — streak đứt, │
│ │ bắt đầu chuỗi mới từ hôm nay) │
│ Key chưa tồn tại │ value = 1 (bắt đầu chuỗi mới) │
└──────────────────────┴──────────────────────────────────────────┘
Lưu ý quan trọng: Khi streak đứt, giá trị được gán thẳng = 1 trong một thao tác atomic duy nhất (không phải gọi RESET rồi INCREMENT riêng). Vì hôm nay user CÓ thực hiện hành vi, nên streak phải là 1 chứ không phải 0.
Ví dụ UC-02 (Daily Login Streak):
INCREMENT login_streak, val: 1, streak: "DAILY"
Threshold: { "==": 7, triggerEvent: "Login_Streak_7_Days" }
Ngày 1 (Thứ Hai): key mới → value = 1
Ngày 2 (Thứ Ba): hôm qua → value = 2
Ngày 3 (Thứ Tư): hôm qua → value = 3
Ngày 3 (Thứ Tư, login lần 2): cùng ngày → BỎ QUA
Ngày 4 (Thứ Năm): hôm qua → value = 4
--- User không login Thứ Sáu ---
Ngày 6 (Thứ Bảy): cách 2 ngày → value = 1 (streak đứt, đếm lại)
Ngày 7 (Chủ Nhật): hôm qua → value = 2
...
Ngày N (liên tiếp 7): hôm qua → value = 7 → Trigger! ✓
Chế độ 3: window — Đếm gộp trong chu kỳ cố định.
Dùng để đếm tổng số lần thực hiện hành vi trong một khoảng thời gian. Hết chu kỳ thì reset, bắt đầu đếm lại từ đầu. Không quan tâm liên tiếp hay không.
Giá trị window hợp lệ: DAILY, WEEKLY, MONTHLY.
window: "WEEKLY"
Tính xem eventTime và last_updated có cùng chu kỳ không:
┌──────────────────────┬──────────────────────────────────────────┐
│ Điều kiện │ Hành vi │
├──────────────────────┼──────────────────────────────────────────┤
│ Cùng tuần (ISO 8601) │ value = value + val (tiếp tục đếm) │
│ Khác tuần │ value = 1 (reset, bắt đầu chu kỳ mới) │
│ Key chưa tồn tại │ value = 1 (bắt đầu chu kỳ mới) │
└──────────────────────┴──────────────────────────────────────────┘
Quy ước chu kỳ:
WEEKLYtính theo ISO 8601 (Thứ Hai là ngày đầu tuần).MONTHLYtính theo tháng dương lịch (ngày 1 là ngày đầu tháng).
Ví dụ UC-03 (View Item — 10 lần xem trong tuần):
INCREMENT weekly_view_count, val: 1, window: "WEEKLY"
Threshold: { "==": 10, triggerEvent: "View_10_Items_Weekly" }
Thứ Hai: xem 3 item → value = 3
Thứ Tư: xem 4 item → value = 7
Thứ Sáu: xem 3 item → value = 10 → Trigger! ✓
--- Sang tuần mới (Thứ Hai tiếp theo) ---
Thứ Hai: xem 1 item → value = 1 (reset, đếm lại từ đầu)
APPEND
Thêm phần tử vào cuối danh sách (List/Array).
| Thuộc tính | Giá trị |
|---|---|
| Kiểu value | Array |
| Key chưa tồn tại | Tạo mới = [], rồi thêm phần tử |
| Tham số tùy chọn | unique (Boolean), maxSize (Number) |
Options:
unique: true— Chỉ thêm nếu phần tử chưa có trong danh sách. Bỏ qua nếu trùng.maxSize: N— Giới hạn kích thước tối đa. Khi đạtmaxSize, xóa phần tử cũ nhất (FIFO) để nhường chỗ cho phần tử mới.
Ví dụ UC-03 (Lưu vết sản phẩm đã xem):
APPEND viewed_items, val: "item_789", unique: true, maxSize: 50
Bước 1: viewed_items = ["item_123", "item_456"]
→ Thêm "item_789"
→ viewed_items = ["item_123", "item_456", "item_789"]
Trùng lặp: APPEND viewed_items, val: "item_123", unique: true
→ "item_123" đã có → BỎ QUA
Tràn maxSize: list có 50 items, thêm "item_new"
→ Xóa phần tử cũ nhất (FIFO): "item_001"
→ Thêm "item_new" vào cuối
→ list vẫn = 50 items
5.4. Threshold (Ngưỡng kích hoạt)
Threshold là điều kiện để Attribute Service quyết định có publish Trigger Event hay không. Mỗi Instruction có thể đính kèm tối đa 1 threshold.
Operators hỗ trợ:
| Operator | Ý nghĩa | Ví dụ |
|---|---|---|
== |
Bằng | login_streak == 7 |
>= |
Lớn hơn hoặc bằng | total_spend >= 5000000 |
> |
Lớn hơn | view_count > 10 |
TIME_GAP_GT |
Khoảng cách thời gian lớn hơn N ngày (so sánh eventTime - old_value) |
last_login_date cách eventTime > 90 ngày |
Quy tắc trigger:
- Mặc định
triggerOnce: true: Threshold chỉ fire 1 lần duy nhất khi value lần đầu thỏa mãn condition. Attribute Service tra cứu bảngthreshold_fire_log(composite key:customer_id+attribute_key+threshold_id) để ngăn trigger lặp lại. Chi tiết xem §5.2b. - Threshold chỉ được evaluate khi value thực sự thay đổi. Nếu operation bị bỏ qua (VD:
SET_TRUE_ONCEkhi đã true, hoặcINCREMENTvớistreakcùng ngày), threshold không được evaluate.
6. Instruction Format & Registry
6.1. Instruction Format (Định dạng Chỉ thị)
Mỗi Instruction trong Fat Payload tuân theo cấu trúc chuẩn sau:
{
"op": "INCREMENT",
"key": "login_streak",
"val": 1,
"streak": "DAILY",
"options": {},
"threshold": {
"condition": "==",
"target": 7,
"triggerOnce": true,
"triggerEvent": "Login_Streak_7_Days"
}
}
| Field | Bắt buộc | Mô tả |
|---|---|---|
op |
✓ | Tên Operation: SET, SET_TRUE_ONCE, INCREMENT, APPEND |
key |
✓ | Tên attribute key (không bao gồm prefix customerId, prefix được tự động thêm bởi Attribute Service) |
val |
✓ | Giá trị truyền vào (Number, String, Boolean tùy op) |
streak |
Chế độ đếm chuỗi liên tiếp. Giá trị: DAILY, WEEKLY, MONTHLY. Chỉ dùng với INCREMENT. Loại trừ với window |
|
window |
Chế độ đếm gộp theo chu kỳ. Giá trị: DAILY, WEEKLY, MONTHLY. Chỉ dùng với INCREMENT. Loại trừ với streak |
|
options |
Tùy chọn bổ sung. Với APPEND: { "unique": true, "maxSize": 50 } |
|
threshold |
Điều kiện kích hoạt Trigger Event. Nếu không có thì Attribute chỉ lưu data, không trigger |
6.2. Registry (Cơ chế Đăng ký Sự kiện)
Toàn bộ "trí tuệ" phân luồng nằm tại cấu hình (Registry) của Event Processor.
Luồng ánh xạ: Thin Event (Client gửi) ──> Registry tra cứu ──> Fat Payload (Chứa Instructions).
Ví dụ Registry: Khi Client gửi sự kiện Login, Registry cấu hình đính kèm 3 Instructions:
SET_TRUE_ONCEcho cờfirst_login— TriggerFirst_Loginnếu lần đầu.INCREMENTvớistreak: "DAILY"chologin_streak— TriggerLogin_Streak_7_Dayskhi đạt 7.SETcholast_login_date = eventTime— TriggerAccount_ReactivatednếuTIME_GAP_GT 90 days.
7. Non-Functional Requirements & Risk Assessment
7.1. Hiệu năng & Mở rộng (Performance & Scalability)
- Tách bạch luồng dữ liệu (Isolation): Việc tách riêng luồng giao dịch cốt lõi (Direct) và luồng theo dõi hành vi (Tracking) giúp Core Engine tập trung 100% công suất cho dòng tiền/điểm.
- Event-Driven & Async processing: Các luồng không yêu cầu phản hồi ngay được đẩy qua Kafka, cho phép các service tự điều chỉnh tốc độ tiêu thụ (throttle) và dễ dàng mở rộng ngang (horizontal scaling) thông qua việc tăng partition của Kafka.
7.2. Bảo mật & Tính toàn vẹn (Security & Data Integrity)
- Idempotency (Tính luỹ đẳng): Transaction Service đảm bảo mọi giao dịch cộng/trừ điểm hoặc cấp thẻ/voucher đều kiểm tra khóa Idempotent, chống duplicate event sinh ra từ mạng chập chờn.
- ACID Transaction: Mọi biến động điểm số hay trạng thái kho (voucher) đều được thực thi và khóa chặt trong 1 Database Transaction nguyên tử.
7.3. Tính khả dụng & Giám sát (Availability & Monitoring)
- Smart Routing & Fallback: Event Processor kiểm tra định dạng Schema của từng sự kiện đầu vào. Các sự kiện lỗi định dạng sẽ bị reject hoặc đẩy vào Dead Letter Queue (DLQ) để điều tra và phục hồi sau.
7.4. Đánh giá Rủi ro Kỹ thuật (Technical Risk Assessment)
1. Rủi ro phức tạp nhất: Đổi quà (UC-09 - Redeem Item)
- Vấn đề: Rất dễ xảy ra lỗi tranh chấp dữ liệu (Race Condition) khi nhiều người dùng cùng đổi 1 món quà đang "hot" có số lượng giới hạn, hoặc timeout giao dịch DB khiến hệ thống "trừ điểm nhưng kho không trừ".
- Phương án dự phòng:
- Áp dụng cơ chế Pessimistic Locking (
SELECT FOR UPDATE) ở bảng Inventory. - Kết hợp sử dụng Redis Decrement để cấp phát số lượng kho tạm thời trên memory trước khi thực hiện giao dịch xuống Database (Pre-deduction).
- Áp dụng cơ chế Pessimistic Locking (
2. Nghẽn cổ chai luồng sự kiện (Bottleneck in High-Frequency Events)
- Vấn đề: Các sự kiện như
View_ItemhayLogincó lượng truy cập khổng lồ, nếu đẩy dồn dập có thể làm chậm trễ các sự kiện quan trọng nhưTransfer_Balancetrên hạ tầng Kafka. - Phương án dự phòng:
- Tách bạch hoàn toàn
tracking-topic(Volume lớn, độ ưu tiên thấp) vàevents-topic(Volume vừa, độ ưu tiên cao). - Tăng số lượng Partitions trên
tracking-topicvà chạy nhiều Instance (Consumer Group) củaattribute-serviceđể tiêu thụ song song (Horizontal Scale).
- Tách bạch hoàn toàn
3. Cơ chế phục hồi sự cố (Fault Tolerance & Dead Letter Queue)
- Vấn đề: Khi
transaction-servicedính ngoại lệ logic (VD: Khách hàng bị khóa tài khoản ngay lúc event trả điểm tới) khiến việc consume báo lỗi liên tục. - Phương án dự phòng:
- Cấu hình Dead Letter Queue (DLQ) trên Kafka. Nếu một sự kiện xử lý thất bại sau số lần retry quy định (VD: 3 lần), hệ thống sẽ gắp nó bỏ qua DLQ để tránh làm nghẽn dòng thông tin (Head-of-line blocking). Đội vận hành sẽ tái xử lý (replay) các sự kiện DLQ này sau khi khắc phục xong lỗi.
Appendix: Bảng tóm tắt 13 Use Cases
| Use Case | Original Event | Type | Operation | Consumer | Output |
|---|---|---|---|---|---|
| UC-01 First Login | Login |
Tracking | SET_TRUE_ONCE |
Attribute ➔ Transaction |
+100đ, In-app msg, OTT |
| UC-02 Daily Login | Login |
Tracking | INCREMENT (streak: DAILY) |
Attribute ➔ Transaction |
+100đ / +500đ |
| UC-03 View Item | ViewItem |
Tracking | INCREMENT (window: WEEKLY) + APPEND |
Attribute ➔ Transaction |
+50đ / Lưu log recommend |
| UC-04 Transfer | Transfer_Balance |
Direct | N/A | Transaction |
Debit/Credit ACID |
| UC-05 Inactive Login | Login |
Tracking | SET (TIME_GAP_GT 90d) |
Attribute ➔ Transaction |
Trigger Account_Reactivated |
| UC-06 Welcome Offer | Welcome_Offer |
Direct | N/A | Transaction |
+100đ |
| UC-07 Update Profile | Profile_Completed |
Direct | N/A | Transaction |
+100đ |
| UC-08 Survey | Survey_Completed |
Tracking | SET_TRUE_ONCE |
Attribute ➔ Transaction |
Tặng voucher |
| UC-09 Redeem | Redeem_Item |
Direct | N/A | Transaction |
Trừ điểm + Reserve item |
| UC-10 Merchant Scan | N/A (Sync API) | Sync | N/A | Transaction |
Đánh dấu voucher USED |
| UC-11 Retention PW | Cronjob_Run |
Notification | N/A | Notification |
Push/Email cảnh báo PW |
| UC-12 Most Redeem | Redeem_Item |
Tracking | INCREMENT (tích lũy) |
Attribute |
Tăng bộ đếm thuần túy |
| UC-13 Dormant User | Cronjob_Run |
Direct | N/A | Transaction |
Tặng ưu đãi + Push Noti |