Skip to content

Architecture

1. Bối cảnh hệ thống (C4 L1)

2. Góc nhìn Container (C4 L2)

Một deployable duy nhất chạy dưới role api, worker, hoặc cả hai, chọn bằng APP_ENV_APPLICATION_ROLES.

3. Góc nhìn Component (C4 L3) - Phân lớp nội bộ

LớpTrách nhiệm
ControllersAuth + assertMerchantAccess + ánh xạ DTO (action/CRUD; tax-tier, tax-config, identifier là CRUD đọc-chủ-yếu trên dữ liệu seed)
Service Queue/SnapshotLogic vòng đời, idempotency, enqueue
Worker serviceĐiều phối pipeline (fetch → render → encrypt → upload)
Fetchers / generators / crypto / metalinkCác pha pipeline đơn trách nhiệm
RepositoriesTruy vấn Drizzle, bộ chỉnh job-status liền mạch, soft-delete
ComponentsKafka producer/consumer, recovery sweep, WS emitter

4. Chỉ mục máy trạng thái

EntityTrạng tháiSơ đồ
LedgerPENDING, NEW, PROCESSING, COMPLETED, FAIL, SETTLED, ARCHIVED→ nhảy tới
LedgerJobPENDING, PROCESSING, COMPLETED, REJECTED (+DRAFT = đã tạo, chưa đẩy queue)→ nhảy tới

* Luồng active do người dùng điều khiển dùng PENDING (draft) ↔ SETTLED (đã finalize); ARCHIVED đánh dấu version bị thay thế. Các trạng thái tạo (NEW/PROCESSING/COMPLETED/FAIL) có định nghĩa trên enum nhưng phần ghi Ledger.status của worker hiện đã được comment.

Ledger (vòng đời do người dùng điều khiển)

Sở hữu độc quyền bởi LedgerSnapshotService. Worker không bao giờ thay đổi Ledger.status.

TừSự kiệnĐếnĐiều kiện
PENDINGfinalizeSETTLEDsnapshot không có hasUnrecordedChange
SETTLEDrevisehàng PENDING mớicần note.default; đặt version+1, previousVersionId

LedgerJob (trạng thái tạo)

Sở hữu bởi LedgerJobService + LedgerWorkerService. Độc lập với Ledger.status.

TừSự kiệnĐếnĐiều kiện
PENDINGsetProcessingPROCESSINGUPDATE … WHERE status=PENDING (một thao tác duy nhất); trả null nếu đã bị claim
PROCESSINGsetCompletedIfProcessingCOMPLETEDWHERE status=PROCESSING (một thao tác duy nhất); false nếu bị giành trước
PROCESSINGlỗi/timeoutREJECTEDghi failureReason (errorCode + i18n)
REJECTEDhandleRetryPENDINGre-enqueue; không reset attemptCount
PROCESSINGkẹt (processStartAt < cutoff)PENDINGquét RecoveryComponent, rồi re-enqueue

5. Kịch bản runtime

5.1 Enqueue → generate → complete

BướcChi tiết
3Idempotent trên (merchantId, type, period); sổ SETTLED từ chối re-generate
8Không có job PENDING → return (đã claim/done) trừ khi APP_ENV_FORCE_GENERATE
11Parse thất bại → REJECTED (FETCH_DATA_ERROR), commit, không replay
14setCompletedIfProcessing=false → bị worker đồng thời giành trước; bỏ qua finalize
16Commit chỉ xảy ra sau upload + finalize; lỗi rethrow và message vẫn được commit (không auto-replay)

5.2 Phục hồi job kẹt

BướcChi tiết
2Chỉ job PROCESSINGprocessStartAt cũ hơn APP_ENV_STALL_THRESHOLD_MS (mặc định 180s)
4RecoveryComponent đăng ký trước KafkaConsumerComponent nên message re-enqueue tồn tại trước khi consumer poll

5.3 Finalize rồi revise

6. Mối quan tâm xuyên suốt

Mối quan tâmCách service này xử lý
AuthNJWT (issuer = identity), JWKS xác minh mỗi request (VerifierApplication)
AuthZCasbin qua PolicyDefinition; permission cache trong Redis; mọi endpoint gọi assertMerchantAccess(merchantId)
i18njsonb { en, vi } (ledger note, identifier/tax-tier/tax-config name/description, failure-reason)
Loggingkey-value có cấu trúc (key: %s); ghi log các pha pipeline (FETCH/GENERATE/UPLOAD/COMPLETED)
IdempotencyEnqueue khoá trên (merchantId, type, period); job claim qua UPDATE có điều kiện trong một thao tác duy nhất
EncryptionAES-256-GCM trên mọi file upload (APP_ENV_LEDGER_ENCRYPTION_KEY)
Soft-deleteSoftDeletableRepository (deletedAt); unique index là partial (WHERE deleted_at IS NULL)
IDsSnowflake qua IdGenerator, worker 6

7. Trang liên quan

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.