Architecture
1. System Context (C4 L1)
Commerce does not push to sister services directly. The integration seam is CDC (Debezium tails the Postgres WAL). See ADR-0002.
2. Container View (C4 L2)
3. Component View (C4 L3) — Internal Layering
| Layer | Responsibility |
|---|---|
| Routes | Base paths in RestPaths |
| Controllers | Auth gate (jwt/basic), role-based filtering, DTO mapping |
| Services | Aggregate transactions, EventBus emission, encryption |
| Repositories | Drizzle queries, soft-delete |
| Components | Kafka producer, BullMQ queue/worker, EventBus, WebSocket, search/asset, two Redis |
4. State Machines Index
| Entity | States | Diagram |
|---|---|---|
Product | DRAFT, ACTIVATED, DEACTIVATED, ARCHIVED | → |
ProductVariant | DRAFT, ACTIVATED, DEACTIVATED, ARCHIVED | → |
Merchant onboarding | per-step boolean flags | → |
Product / ProductVariant status
ARCHIVEDis terminal.Statusesare reused from@venizia/ignis(ProductStatuses/ProductVariantStatuses). ADRAFTvalue also exists in the enum.
ProductVariant.type (structural discriminator)
Not a lifecycle — a type that drives stocking & bundling behavior. Source:
core/.../product-variant/constants.ts.
| Type | Stockable | BOM | Notes |
|---|---|---|---|
STORABLE | ✓ | — | physical, stocked (default) |
CONSUMABLE | — | — | physical, not stocked |
SERVICE | — | — | no physical goods |
KIT | — | ✓ | explodes at sale through Material |
COMBO | — | ✓ | virtual; explodes at sale through component ProductVariant |
MANUFACTURED | ✓ | ✓ | produce-to-stock |
See ADR-0003.
Merchant onboarding steps
MerchantOnboardingStepsflags live inMerchant.metadata.onboarding.ProductAggregateCreatedListener.handleMarkOnboardingStepCompletemarks thePRODUCTstep on first product create.
5. Runtime Scenarios
5.1 Product Aggregate Create + Multi-Merchant Sync
The aggregate transaction commits before the EventBus emit — sync is best-effort/async. See ADR-0001.
5.2 Organizer Onboarding (synchronous)
No
COMMERCE_INITIALIZED_*fan-out queue — downstream setup is CDC-driven.
5.3 Merchant Tax Info → TaxInfo (CDC)
MST/e-invoice is stored in
Merchant.metadata.tax(not a column).TaxInfois the authoritative downstream read source. See merchant tax-info flow.
5.4 Encrypted Provider Integration
6. Crosscutting Concerns
| Concern | How this service handles it |
|---|---|
| AuthN | JWT (ES256, JWKS from identity); BASIC for service-to-service. VerifierApplication. |
| AuthZ | Role-based filtering via useRequestContext() + AppFixedRoles; PolicyDefinition Casbin GROUP rules cached in authorization Redis |
| i18n | jsonb { en, vi } via i18n() columns |
| Logging | IGNIS structured (key: %s) |
| Encryption | EncryptService AES-256-GCM with APP_ENV_APPLICATION_SECRET; getDecryptedCredential internal-only |
| Integration seam | CDC (Debezium WAL) — producer-only Kafka, no app-level produce |
| Async replication | In-process EventBus → BullMQ SYNC_PRODUCT_QUEUE → worker |
| Soft-delete | SoftDeletableRepository (deletedAt) on all entities |
| IDs | Snowflake via IdGenerator, worker 2; slugs auto-generated if absent |
| Dual mode | ApplicationRoles.API / WORKER; CDC consumer + workers only in WORKER role |