Architecture
1. System Context (C4 L1)
2. Container View (C4 L2)
Không có component Redis, BullMQ hay WebSocket. Bề mặt async duy nhất là một CDC consumer.
3. Component View (C4 L3) - Phân lớp nội bộ
| Layer | Trách nhiệm |
|---|---|
| Controllers | Auth (jwt/basic) + chốt kiểm tra permission + map DTO; CRUD qua ControllerFactory |
ApplicationKafkaComponent | Đăng ký CDCKafkaTopics.PRODUCT, giải mã payload Debezium, định tuyến tới worker |
TaxationWorkerService | Ánh xạ op CDC (c/u/r/d) + trạng thái sản phẩm sang provision/deprovision |
TaxProvisioningService | Ghi idempotent TaxSet+Tax từ một template TaxGroup |
TaxGroupService | Kiểm tra tương thích merchant taxMethod ↔ group |
| Repositories | Truy vấn Drizzle trên schema @nx/core; soft-delete |
4. Chỉ mục State Machine
| Entity | Trạng thái | Sơ đồ |
|---|---|---|
TaxSet | ACTIVATED, DEACTIVATED, ARCHIVED | → jump |
TaxSet (vòng đời provisioning)
| Từ | Sự kiện | Đến | Guards |
|---|---|---|---|
[*] | provisionForProduct | ACTIVATED | TaxGroup tồn tại, có ≥1 item |
ACTIVATED | provision lại (group khác) | DEACTIVATED (cũ) | chỉ khi sourceType = TaxGroup |
ACTIVATED | deprovisionForProduct | DEACTIVATED | chỉ khi sourceType = TaxGroup |
ACTIVATED | cùng group | ACTIVATED (no-op) | bỏ qua do idempotent |
Chỉ TaxSet có nguồn từ
TaxGroupmới bị tác động. TaxSet tạo thủ công (ghi đè biến thể, chưa đặtsourceType) không bao giờ bị provisioning deactivate.
5. Kịch bản Runtime
5.1 CDC reconcile khi tạo/cập nhật sản phẩm
| Bước | Chi tiết |
|---|---|
| 2-3 | fallbackMode: latest - chỉ tiêu thụ thay đổi đang phát sinh; không nạp bù lịch sử |
| 4 | Dòng snake_case của Debezium được chuyển qua toCamelCaseKeys |
| 5 | Sản phẩm soft-deleted (deletedAt) sẽ deprovision trước khi kiểm tra taxGroup |
else | Provision idempotent: cùng (sourceType=TaxGroup, sourceId) → bỏ qua |
5.2 CDC delete
5.3 Provisioning thủ công qua REST
6. Mối quan tâm xuyên suốt
| Mối quan tâm | Cách service này xử lý |
|---|---|
| AuthN | JWT (Issuer = identity) + HTTP Basic; strategy ['jwt','basic'] trên mọi route |
| AuthZ | Permission dựa trên resource, seed theo từng controller; cấp cho OWNER/EMPLOYEE/CASHIER khi bootstrap |
| i18n | Cột jsonb i18n('name') ({ default, en, vi }) trên tên TaxGroup, TaxGroupItem, Tax |
| Logging | Key-value có cấu trúc (key: %s); phân vùng logger.for(method) trong service |
| Tracing | Không có tracer riêng |
| Idempotency | Provision bỏ qua khi TaxSet đang active đã có nguồn từ cùng TaxGroup (ADR-0001) |
| Soft-delete | SoftDeletableRepository (deletedAt); deprovision dùng status DEACTIVATED, không bao giờ hard-delete |
| IDs | Snowflake qua IdGenerator, worker id 13 (cố định cứng - nguy cơ trùng id nếu mở rộng) |