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) + cổng permission + map DTO; CRUD qua ControllerFactory |
ApplicationKafkaComponent | Subscribe CDCKafkaTopics.PRODUCT, deserialize payload Debezium, route tới worker |
TaxationWorkerService | Map op CDC (c/u/r/d) + state sản phẩm tới provision/deprovision |
TaxProvisioningService | Ghi idempotent TaxSet+Tax từ một template TaxGroup |
TaxGroupService | Validate 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 | re-provision (group khác) | DEACTIVATED (cũ) | chỉ khi sourceType = TaxGroup |
ACTIVATED | deprovisionForProduct | DEACTIVATED | chỉ khi sourceType = TaxGroup |
ACTIVATED | cùng group | ACTIVATED (no-op) | skip idempotent |
Chỉ TaxSet có nguồn từ
TaxGroupđược chạm tới. TaxSet tạo thủ công (override variant,sourceTypechưa set) 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ỉ consume thay đổi live; không backfill lịch sử |
| 4 | Dòng snake_case Debezium convert qua toCamelCaseKeys |
| 5 | Sản phẩm soft-deleted (deletedAt) deprovision trước khi check taxGroup |
else | Provision idempotent: cùng (sourceType=TaxGroup, sourceId) → skip |
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 resource-based 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); scope logger.for(method) trong service |
| Tracing | Không có tracer riêng |
| Idempotency | Provision skip khi TaxSet 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 (hardcode — rủi ro collision nếu scale) |