Architecture
1. Bối cảnh hệ thống (C4 L1)
2. Góc nhìn Container (C4 L2)
Không tồn tại container Kafka consumer, BullMQ worker, hay WebSocket emitter — service là một process REST duy nhất cộng một entry migration.
3. Góc nhìn Component (C4 L3) — Phân lớp nội bộ
| Lớp | Trách nhiệm |
|---|---|
| Routes | Bề mặt HTTP, khai báo trong RestPaths + definitions.ts theo từng controller |
| Controllers | ControllerFactory.defineCrudController (Policy/PolicyFeature/License/Activation) + handler vòng đời @post tuỳ chỉnh; cổng auth + permission |
| Services | Business logic, transaction, re-publish chứng chỉ (LicenseManagementService, ActivationService, ValidationService) |
LicensingBaseService | Base dùng chung: sinh key, giải feature, publishCertificate(), logEvent(), createActivation() |
| Repositories | Re-export từ @nx/core; soft-delete qua generateCommonColumnDefs |
| Components | Chỉ Redis cache (phân phối cert + cache authz) |
4. Chỉ mục máy trạng thái
| Entity | Trạng thái | Sơ đồ |
|---|---|---|
License | ACTIVATED, SUSPENDED, EXPIRED, REVOKED | → nhảy tới |
PolicyvàPolicyFeaturedùng status vòng đời chung (ACTIVATED/DEACTIVATED/ARCHIVED) không có chuyển trạng thái có guard.Activationkhông có cột status — vòng đời của nó là create/delete.
License
| Từ | Sự kiện | Đến | Điều kiện |
|---|---|---|---|
* (mới) | issue | ACTIVATED | Policy phải tồn tại; sinh key; publish cert |
ACTIVATED | suspend | SUSPENDED | Phải isActive() nếu không SUSPEND_INVALID_STATUS |
SUSPENDED | reinstate | ACTIVATED | Phải SUSPENDED nếu không REINSTATE_INVALID_STATUS |
ACTIVATED | validate (lazy) | EXPIRED | expiresAt < now VÀ qua graceExpiresAt; UPDATE ... WHERE status=ACTIVATED có điều kiện |
ACTIVATED/EXPIRED | renew | ACTIVATED | Từ chối REVOKED/SUSPENDED; Policy phải có duration (nếu không RENEW_PERPETUAL) |
bất kỳ không-REVOKED | revoke | REVOKED | Guard idempotent REVOKE_ALREADY_REVOKED |
Hết hạn là lazy. Không có reaper định kỳ. Một license lật sang
EXPIREDchỉ khiValidationService.validate()chạy và quan sát thấyexpiresAtvà cửa sổ grace đã qua cả hai.
5. Kịch bản runtime
5.1 Phát hành một license
5.2 Xác thực một key (lazy expiry + activation ngầm)
5.3 Kích hoạt một thiết bị (giới hạn seat an toàn với race)
5.4 Tiêu thụ chứng chỉ (service khác)
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) hoặc HTTP Basic — [AuthenticateStrategy.JWT, AuthenticateStrategy.BASIC] trên mọi route |
| AuthZ | Casbin qua PolicyDefinition; permission seed bởi migration 0001/0003/0004. Route read License tạm authorize.skip (ADR-0003) |
| i18n | cột jsonb i18n ({ default, en, vi }) trên Policy.name/description, PolicyFeature.name/description, License.name |
| Logging | Có cấu trúc, key-value (key: %s); scope theo method qua this.logger.for(name) |
| Tracing | No-op (chưa wire tracer) |
| Idempotency | Fast-path activation (find theo licenseId+fingerprint trước lock); issueFreeTrial trả về trial có sẵn; lật expiry qua UPDATE có điều kiện; partial-unique index trên (licenseId, fingerprint) |
| Concurrency | SELECT … FOR UPDATE row-lock trên hàng License cho mọi mutation vòng đời và kiểm giới hạn activation |
| Soft-delete | License, Policy, Activation mang deletedAt (qua generateCommonColumnDefs); PolicyFeature & LicenseEvent tắt cột deleted |
| IDs | Snowflake qua IdGenerator, worker 11 |