Architecture
1. System Context (C4 L1)
Pricing is purely synchronous on the read/calculate path (sale calls it inline at checkout). The only async input is the
ProductVariantCDC stream that auto-seeds fares.
2. Container View (C4 L2)
3. Component View (C4 L3) — Internal Layering
| Layer | Responsibility |
|---|---|
| Routes | 12 base paths in RestPaths (one — pricing-configurations — declared, no controller mounted) |
| Controllers | Auth gate (jwt, basic), resource-based permission, DTO mapping |
| Calculation services | Stateless fare/tax math; v1 flat, v2 snapshot |
| Management services | CRUD + aggregate transactions |
| Repositories | Drizzle queries, soft-delete, counter denormalization |
| Worker | CDC ingestion, idempotent fare seeding |
4. State Machines Index
| Entity | States | Diagram |
|---|---|---|
FareSet | ACTIVATED, DEACTIVATED | → |
Fare | ACTIVATED, DEACTIVATED, ARCHIVED | → |
Tax / TaxSet / TaxType | ACTIVATED, DEACTIVATED, ARCHIVED | → |
Promotion | DRAFT, ACTIVATED, DEACTIVATED, ARCHIVED | → |
FareSet
FareSetStatuses.canDeactivate()always returnsfalse— an activated FareSet is terminal-active. Exactly one ACTIVATED FareSet per variant is the selection invariant.
Fare
Tax / TaxSet / TaxType
All three share IGNIS
Statuses.{ACTIVATED, DEACTIVATED, ARCHIVED}. The tax calculator only reads ACTIVATED rows within their effective window.
Promotion
Promotions are CRUD-only today — no calculator consumes them, so state has no pricing effect yet.
5. Runtime Scenarios
5.1 Checkout Pricing (v2 snapshot)
| Step | Detail |
|---|---|
| 2–7 | Per-line: fare selected (DEFAULT/OVERRIDE/POLICY), then compound priority-grouped taxes |
| 8 | Order snapshot aggregates per-line byBearer ledgers + order-level rules |
| 9–10 | Sale persists the immutable snapshot; pricing keeps no order state |
5.2 ProductVariant CDC → Fare Seeding
| Step | Detail |
|---|---|
| 4 | Only CREATE/RETRIEVE handled; UPDATE/DELETE are no-ops |
| 5 | Skips when a FareSet already exists for the variant (idempotent) |
| 6 | FBT bundlers seed an OVERRIDE child carrying orderProductVariantIds CONTAINS leadVariantId |
5.3 Synced Variant — FareSet Deep Copy
When a variant carries
metadata.referenceId, the worker deep-copies the reference variant's full fare structure inside one transaction instead of seeding a fresh default.
6. Crosscutting Concerns
| Concern | How this service handles it |
|---|---|
| AuthN | JWT (ES256, JWKS from identity); BASIC for service-to-service (sale uses BASIC) |
| AuthZ | Casbin via PolicyDefinition; resource-based per controller; permissions cached in Redis |
| i18n | jsonb { default, en, vi } on name / description columns |
| Logging | IGNIS structured (key: %s); @logged() decorators; SKIP/DONE markers in worker |
| Money | float(x, 4) everywhere; amounts stored as decimal(15,4), serialized as strings (MoneySchema) |
| Idempotency | CDC worker skips when FareSet/FBT override already seeded; calculate endpoints are side-effect-free |
| Statelessness | Pricing persists no order/snapshot; the snapshot it returns is owned by the caller |
| Soft-delete | SoftDeletableRepository (deletedAt); default filter deletedAt IS NULL |
| IDs | Snowflake via IdGenerator, worker 7 |