Architecture
1. System Context (C4 L1)
2. Container View (C4 L2)
Không có Kafka consumer, không có BullMQ worker. Fan-out nền duy nhất là WebSocket emitter backed bởi Redis.
3. Component View (C4 L3) — Phân lớp nội bộ
| Layer | Trách nhiệm |
|---|---|
| Routes | Bề mặt HTTP; route tùy chỉnh khai báo trong RestPaths + definitions.ts của controller |
| Controllers | Auth + cổng permission, map DTO, fire WS notify khi submit |
| Services | Logic subscribe/unsubscribe/stats của SubscriberService |
| Repositories | Truy vấn Drizzle, soft-delete, rollup getStatistics() |
| Components | ApplicationWebSocketComponent — Redis emitter + socket event service |
4. Chỉ mục State Machine
| Entity | Trạng thái | Sơ đồ |
|---|---|---|
Inquiry | NEW, PROCESSING, COMPLETED, CLOSED, CANCELLED | → jump |
Subscriber | ACTIVATED, DEACTIVATED, ARCHIVED | → jump |
Cả hai dùng IGNIS
Statuses. Chuyển trạng thái không được máy thực thi — admin di chuyểnInquirytự do qua CRUD update; chuyển trạng tháiSubscriberđược điều khiển bởiSubscriberService(subscribe ↔ unsubscribe). Các sơ đồ dưới đây thể hiện vòng đời dự kiến.
Inquiry
| Từ | Sự kiện | Đến | Guards |
|---|---|---|---|
[*] | POST /inquiries/submit | NEW | status mặc định |
NEW | admin update | PROCESSING | không (thủ công) |
PROCESSING | admin update | COMPLETED / CLOSED | không (thủ công) |
* | admin update | CANCELLED | không (thủ công) |
Subscriber
| Từ | Sự kiện | Đến | Guards |
|---|---|---|---|
[*] | subscribe (không có dòng) | ACTIVATED | email unique |
ACTIVATED | subscribe (active hiện có) | ACTIVATED | idempotent — trả về dòng hiện có |
DEACTIVATED | subscribe | ACTIVATED | reactivate, clear unsubscribedAt |
ACTIVATED | unsubscribe(token) | DEACTIVATED | token phải resolve về một dòng |
5. Kịch bản Runtime
5.1 Submit yêu cầu + notify real-time
| Bước | Chi tiết |
|---|---|
| 1-3 | Yêu cầu lưu với status=NEW, type=000_CONSULT mặc định trừ khi override |
| 4-6 | WS notify là fire-and-forget (không await); skip nếu emitter chưa ready |
| 6 | Broadcast tới outreach/inquiries và outreach/inquiries/{id} qua Promise.allSettled |
| 7 | Trả 201 bất kể kết quả 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ý |
|---|---|
| AuthN | JWT (Issuer = identity), JWKS verify mỗi request; endpoint public skip auth |
| AuthZ | Permission Casbin seed qua migration; CRUD + stats có cổng, submit/subscribe/unsubscribe public mở |
| i18n | Subscriber.locale (vi/en) chọn ngôn ngữ newsletter; nhãn permission mang { en, vi } |
| Logging | Key-value có cấu trúc (key: %s); WS notify log inquiry id + rooms |
| Tracing | No-op (chưa nối tracer) |
| Idempotency | subscribe idempotent theo email; unsubscribe idempotent (áp lại DEACTIVATED là 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 |