7.1 KiB
7.1 KiB
🏗️ Orders Domain Restructuring Plan
Date: October 2025
Status: Proposed
Priority: High – Architectural Consistency
Why Split Validation?
Orders has two very different validation layers:
- Schema validation – structural guarantees we can infer from Zod (e.g. shape of an incoming request or the normalization of a Salesforce record).
- Business rules – SKU combinations, prerequisite checks, and cross-field rules that need to be reused by both the BFF and frontend logic.
Keeping those concerns in separate modules (*.schema.ts vs rules.ts) mirrors how other domains expose reusable behaviour (customer/validation.ts, sim/rules.ts) and makes it possible to import just the pure rule helpers in environments where running the full schema is overkill. It also prevents circular imports when business logic needs to call back into provider helpers.
Design Goals
- Align the orders domain with the schema-first, provider-isolated structure used by
customer,catalog, andsim. - Make the distinction between raw vendor data, normalized domain models, and business rules obvious.
- Centralise dynamic Salesforce field-map handling so configuration is validated once and shared by mappers and query builders.
- Improve testability by isolating pure transforms and validation helpers.
Current Pain Points
| Area | Issue |
|---|---|
| Contracts | contract.ts mixes business constants with Salesforce configuration interfaces and schema re-exports. |
| Schemas | schema.ts blends read models, write models, fulfillment data, and business refinements in a single block, making reuse awkward. |
| Providers | The Salesforce mapper relies on unchecked configuration using Reflect.get, and WHMCS mapping redefines schemas inline. |
| Config | Query helpers and the BFF maintain separate field lists, so config drift is likely. |
Target File Layout
packages/domain/orders/
├── contract.ts # Business enums/constants only
├── index.ts # Public API surface
├── rules.ts # Domain business helpers (SKU rules, etc.)
├── schemas/
│ ├── read-model.schema.ts # OrderSummary/OrderDetails
│ ├── fulfillment.schema.ts # Fulfillment-specific schemas
│ ├── write-model.schema.ts # CreateOrderRequest, OrderConfigurations
│ └── query.schema.ts # Request/query params shared by API
├── providers/
│ ├── salesforce/
│ │ ├── field-map.schema.ts # Zod schema + helpers for config
│ │ ├── raw.schema.ts # Zod schemas for raw Order/OrderItem/Product2
│ │ ├── normalizer.ts # Converts raw records using field resolver
│ │ ├── mapper.ts # Maps normalised data to read-model schemas
│ │ └── query.ts # Builds SOQL select lists via resolver
│ └── whmcs/
│ ├── raw.schema.ts # Zod schemas for WHMCS payloads/responses
│ └── mapper.ts # Uses fulfillment schema to build payloads
└── validation.ts (exports from rules.ts for backwards compatibility)
Note
: The
schemas/folder keeps runtime validation and inferred types colocated but organised by concern.
Workstreams & Tasks
1. Core Domain Modules
- Rewrite
contract.tsto exposeORDER_TYPE,ORDER_STATUS,ACTIVATION_TYPE, and other business constants only. - Create
schemas/read-model.schema.ts,schemas/write-model.schema.ts,schemas/fulfillment.schema.ts, andschemas/query.schema.ts.- Move relevant sections from the existing
schema.tsinto those files. - Export inferred types alongside each schema.
- Move relevant sections from the existing
- Introduce
rules.tshousing SKU/business helpers currently invalidation.ts.- Keep
validation.tsas a thin re-export for compatibility (mark for deprecation).
- Keep
- Update
index.tsto surface the new modules explicitly.
2. Salesforce Provider Pipeline
- Add
field-map.schema.tswith a Zod schema that validates the dynamic field map coming from configuration.- Export a
createFieldResolver(config)helper that normalises and memoises accessors.
- Export a
- Replace
field-map.mapper.tswithraw.schema.ts,normalizer.ts, andmapper.ts.- Parse raw Salesforce records via
raw.schema.ts. - Apply the field resolver in
normalizer.tsto produce typed intermediate structures. - Feed intermediates into read-model schemas inside
mapper.ts(noReflect.get).
- Parse raw Salesforce records via
- Update
query.tsto consume the same resolver so the BFF and mapper stay in sync.
3. WHMCS Provider
- Replace inline Zod definitions in
providers/whmcs/mapper.tswith imports fromschemas/fulfillment.schema.ts. - Ensure payload building reuses shared helpers (e.g. billing-cycle normaliser) and export them for testing.
- Expand
raw.schema.tsto cover response parsing where we currently trust types.
4. Public API & Backwards Compatibility
- Adjust
packages/domain/orders/index.tsto export:- Business constants from
contract.ts. - Schemas/types from
schemas/*. - Business helpers from
rules.ts. - Provider modules via a consolidated
Providersnamespace.
- Business constants from
- Provide compatibility re-exports for previous paths (
validation.ts, existing provider raw types) and document deprecations.
5. Integration Touchpoints
- Update
apps/bff/src/core/config/field-map.tsto importcreateFieldResolverand use the generated select lists. - Update services (
order-orchestrator,order-validator) to consume new exports (rules, resolver helpers). - Add focused unit tests around the resolver, Salesforce normaliser, and WHMCS mapper changes.
Implementation Order
- Create new schema modules alongside the existing
schema.ts, export them, and update imports progressively. - Introduce the Salesforce field-map schema/resolver and migrate providers/BFF to use it.
- Refactor provider mappers to run through the new normaliser/mapper pipeline.
- Trim the legacy modules once callers are updated (delete old
schema.tssections, replace with barrel exports). - Finalize
index.tsand deprecation notes, then run type checks/tests.
Acceptance Criteria
- Orders domain mirrors the modular structure outlined above.
- Field-map configuration fails fast when misconfigured and is reused everywhere.
- Provider mappers no longer rely on unchecked property access.
- Business rule helpers are in
rules.tswith tests demonstrating reuse. - BFF compiles against the new API surface, and manual order flows pass.
Next Steps
- Socialise this plan with the team and confirm the module naming/placement fits the broader architecture.
- Kick off Workstream 1 to get the new schemas and constants in place.
- Build the Salesforce resolver and migrate the BFF configuration service.
- Continue through the remaining workstreams, validating with tests at each step.
Ready to proceed once reviewers sign off on the structure. 🚀