Architecture
1. System Context (C4 L1)
2. Container View (C4 L2)
Không có Kafka consumer, không có BullMQ worker. Cơ chế fan-out nền duy nhất là WebSocket emitter dựa trên Redis.
3. Component View (C4 L3) - Phân lớp nội bộ
| Lớp | Trách nhiệm |
|---|---|
| Routes | Giao diện HTTP; các route tuỳ chỉnh khai báo trong RestPaths + definitions.ts của controller |
| Controllers | Xác thực + chốt kiểm tra permission, ánh xạ DTO, kích hoạt thông báo WS khi gửi |
| Services | Logic đăng ký/huỷ đăng ký/thống kê của SubscriberService |
| Repositories | Truy vấn Drizzle, soft-delete, tổng hợp getStatistics() |
| Components | ApplicationWebSocketComponent - Redis emitter + socket event service |
4. Chỉ mục máy trạng thái
| Entity | Trạng thái | Sơ đồ |
|---|---|---|
Inquiry | NEW, PROCESSING, COMPLETED, CLOSED, CANCELLED | → jump |
Subscriber | ACTIVATED, DEACTIVATED, ARCHIVED | → jump |
Cả hai dùng IGNIS
Statuses. Việc chuyển trạng thái không được máy ép buộc - admin chuyểnInquirytự do qua CRUD update; còn các chuyển trạng thái củaSubscriberđược điều khiển bởiSubscriberService(đăng ký ↔ huỷ đăng ký). Các sơ đồ dưới đây thể hiện vòng đời dự kiến.
Inquiry
| Từ | Sự kiện | Đến | Điều kiện |
|---|---|---|---|
[*] | POST /inquiries/submit | NEW | status mặc định |
NEW | admin cập nhật | PROCESSING | không (thủ công) |
PROCESSING | admin cập nhật | COMPLETED / CLOSED | không (thủ công) |
* | admin cập nhật | CANCELLED | không (thủ công) |
Subscriber
| Từ | Sự kiện | Đến | Điều kiện |
|---|---|---|---|
[*] | subscribe (chưa có dòng) | ACTIVATED | email duy nhất |
ACTIVATED | subscribe (đang hoạt động) | ACTIVATED | idempotent - trả về dòng hiện có |
DEACTIVATED | subscribe | ACTIVATED | kích hoạt lại, xoá unsubscribedAt |
ACTIVATED | unsubscribe(token) | DEACTIVATED | token phải trỏ về một dòng |
5. Kịch bản Runtime
5.1 Gửi yêu cầu + thông báo theo thời gian thực
| Bước | Chi tiết |
|---|---|
| 1-3 | Yêu cầu được lưu với status=NEW, type=000_CONSULT mặc định trừ khi ghi đè |
| 4-6 | Thông báo WS theo kiểu fire-and-forget (không await); bỏ qua nếu emitter chưa sẵn sàng |
| 6 | Phát tới outreach/inquiries và outreach/inquiries/{id} qua Promise.allSettled |
| 7 | Trả 201 bất kể kết quả của WS |
5.2 Subscribe (idempotent)
5.3 Unsubscribe (token)
6. Mối quan tâm xuyên suốt
| Mối quan tâm | Cách service này xử lý |
|---|---|
| Xác thực (AuthN) | JWT (Issuer = identity), JWKS được xác thực ở mỗi request; các endpoint công khai bỏ qua xác thực |
| Phân quyền (AuthZ) | Permission của Casbin được seed qua migration; CRUD + thống kê có kiểm tra quyền, còn submit/subscribe/unsubscribe công khai để mở |
| i18n | Subscriber.locale (vi/en) chọn ngôn ngữ newsletter; nhãn permission mang { en, vi } |
| Logging | Dạng key-value có cấu trúc (key: %s); thông báo WS ghi log inquiry id + danh sách phòng |
| Tracing | Không hoạt động (chưa nối tracer) |
| Idempotency | subscribe idempotent theo email; unsubscribe idempotent (áp lại DEACTIVATED vẫn an toàn) |
| Soft-delete | SoftDeletableRepository (deletedAt); filter mặc định deletedAt IS NULL |
| IDs | Snowflake qua IdGenerator, worker 10; unsubscribeToken cũng là một Snowflake |