Skip to content

Architecture

1. System Context (C4 L1)

2. Container View (C4 L2)

No Redis, BullMQ, or WebSocket components. The only async surface is the single CDC consumer.

3. Component View (C4 L3) — Internal Layering

LayerResponsibility
ControllersAuth (jwt/basic) + permission gate + DTO mapping; CRUD via ControllerFactory
ApplicationKafkaComponentSubscribes CDCKafkaTopics.PRODUCT, deserializes Debezium payload, routes to worker
TaxationWorkerServiceMaps CDC op (c/u/r/d) + product state to provision/deprovision
TaxProvisioningServiceIdempotent TaxSet+Tax write from a TaxGroup template
TaxGroupServiceMerchant taxMethod ↔ group compatibility validation
RepositoriesDrizzle queries over @nx/core schemas; soft-delete

4. State Machines Index

EntityStatesDiagram
TaxSetACTIVATED, DEACTIVATED, ARCHIVED→ jump

TaxSet (provisioning lifecycle)

FromEventToGuards
[*]provisionForProductACTIVATEDTaxGroup exists, has ≥1 item
ACTIVATEDre-provision (different group)DEACTIVATED (old)only when sourceType = TaxGroup
ACTIVATEDdeprovisionForProductDEACTIVATEDonly when sourceType = TaxGroup
ACTIVATEDsame groupACTIVATED (no-op)idempotent skip

Only TaxGroup-sourced TaxSets are touched. Manually-created TaxSets (variant overrides, sourceType unset) are never deactivated by provisioning.

5. Runtime Scenarios

5.1 CDC reconcile on product create/update

StepDetail
2-3fallbackMode: latest — only live changes consumed; no historical backfill
4Debezium snake_case row converted via toCamelCaseKeys
5Soft-deleted product (deletedAt) deprovisions before taxGroup check
elseProvision is idempotent: same (sourceType=TaxGroup, sourceId) → skip

5.2 CDC delete

5.3 Manual provisioning via REST

6. Crosscutting Concerns

ConcernHow this service handles it
AuthNJWT (Issuer = identity) + HTTP Basic; strategies ['jwt','basic'] on every route
AuthZResource-based permissions seeded per controller; granted to OWNER/EMPLOYEE/CASHIER at bootstrap
i18ni18n('name') jsonb columns ({ default, en, vi }) on TaxGroup, TaxGroupItem name, Tax name
LoggingStructured key-value (key: %s); logger.for(method) scoping in services
TracingNo dedicated tracer
IdempotencyProvision skips when active TaxSet already sourced from the same TaxGroup (ADR-0001)
Soft-deleteSoftDeletableRepository (deletedAt); deprovision uses status DEACTIVATED, never hard-delete
IDsSnowflake via IdGenerator, worker id 13 (hardcoded — collision risk if scaled)

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