13 KiB
Orders Domain & BFF Integration - Architecture Review
Executive Summary
After comprehensive review, there are significant architectural issues that need addressing. The current structure mixes infrastructure concerns with business logic, has types in wrong locations, and the field configuration system is problematic.
🚨 Critical Issues Found
1. Field Configuration in Domain (WRONG PLACE)
Issue: SalesforceOrdersFieldConfig interfaces are defined in the domain package.
Location: packages/domain/orders/contract.ts (lines 98-177)
// Currently in domain - THIS IS WRONG
export interface SalesforceOrderMnpFieldConfig { ... }
export interface SalesforceOrderFieldConfig { ... }
export interface SalesforceOrdersFieldConfig { ... }
Why This Is Wrong:
- These are infrastructure configuration, not business types
- They map logical field names to Salesforce custom field API names
- They're deployment-specific (which fields exist in which Salesforce org)
- They're NOT business concepts - customers don't care about field mappings
- Domain should be provider-agnostic
What Should Happen:
- ❌ Domain defines: "Here are the Salesforce field names you must use"
- ✅ Domain defines: "Here are the business concepts"
- ✅ BFF/Integration layer: "Here's how we map those concepts to Salesforce"
Correct Location: apps/bff/src/integrations/salesforce/types/field-config.types.ts
2. SalesforceFieldConfigService Location (CORRECT NOW)
Current Location: apps/bff/src/integrations/salesforce/services/salesforce-field-config.service.ts ✅
Status: This is CORRECT! You were right to question it.
Why:
- Reads from environment variables (ConfigService)
- Deployment/environment specific
- Infrastructure concern, not business logic
- Belongs in BFF integration layer
3. Pub/Sub Event Types (CORRECT)
Current Location: apps/bff/src/integrations/salesforce/types/pubsub-events.types.ts ✅
Status: This is CORRECT!
Why:
- Salesforce-specific infrastructure types
- Platform event structure, not business events
- Integration concern, not domain concern
- Belongs in BFF integration layer
4. Type Duplication & Confusion
Found Multiple Similar Types:
// In BFF order services
export interface OrderItemMappingResult { ... } // order-whmcs-mapper.service.ts
export interface OrderFulfillmentStep { ... } // order-fulfillment-orchestrator.service.ts
export interface OrderFulfillmentContext { ... } // order-fulfillment-orchestrator.service.ts
export interface SimFulfillmentRequest { ... } // sim-fulfillment.service.ts
// In Domain
export type FulfillmentOrderProduct { ... }
export type FulfillmentOrderItem { ... }
export type FulfillmentOrderDetails { ... }
Issue: Clear separation between:
- Business/Domain types (what an order is)
- Workflow/Process types (how fulfillment works)
- Integration types (how we talk to external systems)
📋 Recommended Architecture
Domain Layer (packages/domain/orders/)
Should contain:
// Business entities
- OrderDetails
- OrderSummary
- OrderItemDetails
- OrderItemSummary
- FulfillmentOrderDetails (if this is a business concept)
// Business rules
- Order validation schemas
- Business constants (ORDER_TYPE, ORDER_STATUS, etc.)
- Order lifecycle states
// Provider contracts (interfaces only, no config)
- What data structure do Salesforce/WHMCS need?
- NOT which fields to use
Should NOT contain:
- ❌ Field configuration interfaces
- ❌ Environment-specific mappings
- ❌ Query helpers (buildOrderSelectFields, etc.)
- ❌ Integration-specific types
BFF Integration Layer (apps/bff/src/integrations/salesforce/)
Should contain:
// Infrastructure configuration
- SalesforceFieldConfigService ✅ (already here)
- Field mapping types (currently in domain ❌)
- Query builders (should move from domain)
- Connection management
- Pub/Sub types ✅ (already here)
// Integration adapters
- Transform business types → Salesforce records
- Transform Salesforce records → business types
Structure:
apps/bff/src/integrations/salesforce/
├── services/
│ ├── salesforce-connection.service.ts
│ ├── salesforce-field-config.service.ts ✅
│ ├── salesforce-account.service.ts
│ └── salesforce-order.service.ts ← NEW (extract from modules/orders)
├── types/
│ ├── field-config.types.ts ← MOVE HERE from domain
│ ├── pubsub-events.types.ts ✅
│ └── query-builder.types.ts ← NEW
└── utils/
├── soql.util.ts
└── query-builder.util.ts ← MOVE buildOrderSelectFields here
BFF Order Module (apps/bff/src/modules/orders/)
Should contain:
// HTTP/API layer
- OrdersController (API endpoints)
// Application services (orchestration)
- OrderOrchestrator (coordinates everything)
- OrderValidator (business + integration validation)
- OrderFulfillmentOrchestrator
// Workflow types (NOT business types)
- OrderFulfillmentContext
- OrderFulfillmentStep
- SimFulfillmentRequest
Should NOT directly call:
- ❌ Direct Salesforce queries (use SalesforceOrderService)
- ❌ Field mapping logic (use SalesforceFieldConfigService)
🔧 Specific Refactorings Needed
1. Move Field Config Types Out of Domain
FROM: packages/domain/orders/contract.ts
export interface SalesforceOrderMnpFieldConfig { ... }
export interface SalesforceOrderBillingFieldConfig { ... }
export interface SalesforceOrderFieldConfig { ... }
export interface SalesforceOrderItemFieldConfig { ... }
export interface SalesforceOrdersFieldConfig { ... }
TO: apps/bff/src/integrations/salesforce/types/field-config.types.ts
/**
* Salesforce Field Configuration Types
* Maps logical business field names to Salesforce custom field API names
*/
export interface SalesforceOrderFieldConfig { ... }
export interface SalesforceOrdersFieldConfig { ... }
// etc.
2. Move Query Builders Out of Domain
FROM: packages/domain/orders/providers/salesforce/query.ts
export function buildOrderSelectFields(...)
export function buildOrderItemSelectFields(...)
export function buildOrderItemProduct2Fields(...)
TO: apps/bff/src/integrations/salesforce/utils/order-query-builder.ts
/**
* SOQL Query builders for Orders
* Uses field configuration to build dynamic queries
*/
export function buildOrderSelectFields(...)
export function buildOrderItemSelectFields(...)
// etc.
Why: These are SOQL-specific, infrastructure concerns, not business logic.
3. Create SalesforceOrderService
NEW: apps/bff/src/integrations/salesforce/services/salesforce-order.service.ts
@Injectable()
export class SalesforceOrderService {
constructor(
private connection: SalesforceConnection,
private fieldConfig: SalesforceFieldConfigService,
private logger: Logger
) {}
async getOrderById(orderId: string): Promise<OrderDetails | null> {
// Contains Salesforce-specific query logic
// Uses field config to build queries
// Transforms SF records → domain types
}
async createOrder(orderData: CreateOrderRequest): Promise<{ id: string }> {
// Transform domain → SF record
// Create in Salesforce
}
async listOrders(params: OrderQueryParams): Promise<OrderSummary[]> {
// Query logic
}
}
Benefits:
- Encapsulates all Salesforce order operations
- OrderOrchestrator doesn't need to know about Salesforce internals
- Easier to test
- Easier to swap providers
4. Consolidate WHMCS Mapping
Current: Duplicate mapping logic in:
packages/domain/orders/providers/whmcs/mapper.ts(domain ✅)apps/bff/src/modules/orders/services/order-whmcs-mapper.service.ts(BFF wrapper)
Issue: The service is just a thin wrapper that calls domain mapper. Either:
Option A: Keep mapper in domain, remove BFF service Option B: Move mapper to BFF integration layer
Recommendation: Option A - Domain mapper is fine for data transformation.
📊 Layering Principles
┌─────────────────────────────────────────┐
│ HTTP Layer (Controller) │
│ - API endpoints │
│ - Request/Response formatting │
└──────────────────┬──────────────────────┘
│
┌──────────────────▼──────────────────────┐
│ Application Layer (Orchestrators) │
│ - OrderOrchestrator │
│ - OrderFulfillmentOrchestrator │
│ - Workflow coordination │
└──────────────────┬──────────────────────┘
│
┌────────────┴────────────┐
│ │
┌─────▼──────────┐ ┌────────▼──────────┐
│ Domain Layer │ │ Integration Layer │
│ (Business) │ │ (Infrastructure) │
│ │ │ │
│ - OrderDetails │ │ - SF Order Service│
│ - Validation │ │ - WHMCS Service │
│ - Rules │ │ - Field Config │
│ - Schemas │ │ - Query Builders │
└────────────────┘ └───────────────────┘
Rules:
- ✅ Domain can depend on: Nothing external
- ✅ Integration can depend on: Domain
- ✅ Application can depend on: Domain + Integration
- ✅ HTTP can depend on: Application
Anti-patterns currently present:
- ❌ Domain exports query builders (infrastructure concern)
- ❌ Domain defines field configuration types (deployment concern)
- ❌ Application services directly query Salesforce (should go through integration service)
🎯 Recommended Action Plan
Phase 1: Move Infrastructure Types Out of Domain (High Priority)
- Create
apps/bff/src/integrations/salesforce/types/field-config.types.ts - Move all
Salesforce*FieldConfiginterfaces from domain to BFF - Update imports across codebase
- Update
SalesforceFieldConfigServiceto export types from new location
Phase 2: Extract Salesforce Order Service (Medium Priority)
- Create
SalesforceOrderServicein integration layer - Move all Salesforce query logic from
OrderOrchestratorto new service - Move query builders from domain to integration utils
- Update orchestrator to use new service
Phase 3: Clean Up Type Exports (Medium Priority)
- Review all domain exports
- Remove infrastructure types from domain exports
- Create clear separation: business types vs workflow types vs integration types
- Document what belongs where
Phase 4: Consolidate Mappers (Low Priority)
- Decide: Keep WHMCS mappers in domain or move to integration
- Remove redundant service wrappers
- Standardize mapper patterns
💡 Key Insights
Your Questions Answered:
-
"Should SalesforceFieldConfigService be in domain or BFF?"
- Answer: BFF integration layer ✅ (already correct!)
- It's environment-specific configuration, not business logic
-
"Should PubSub types be in domain?"
- Answer: No, BFF integration layer ✅ (already correct!)
- They're Salesforce platform events, not business events
-
"There are so many overlapping types and wrong used"
- Answer: YES! Field config interfaces should NOT be in domain
- Query builders should NOT be in domain
- These are infrastructure concerns masquerading as business logic
✅ What's Already Good
- ✅ Core business types (OrderDetails, OrderSummary) are in domain
- ✅ Validation schemas are in domain
- ✅ Business constants (ORDER_TYPE, ORDER_STATUS) are in domain
- ✅ SalesforceFieldConfigService is in BFF (correct location)
- ✅ Pub/Sub types are in BFF (correct location)
- ✅ Raw provider types (SalesforceOrderRecord) are in domain (for portability)
🎓 Architecture Philosophy
Domain Layer:
"What does the business care about? What are the rules?"
Integration Layer:
"How do we talk to external systems? How do we map our concepts to theirs?"
Application Layer:
"What workflows do we support? How do we coordinate services?"
HTTP Layer:
"How do we expose functionality via API?"
Conclusion
The order domain needs refactoring to properly separate:
- Business logic (domain)
- Infrastructure configuration (BFF integration)
- Workflow orchestration (BFF application)
The most critical issue is that field configuration types are in the domain when they should be in the BFF integration layer. This violates domain-driven design principles and creates unwanted coupling.
The good news: You were 100% right to question this, and the major pieces (service locations) are already correct. We just need to move the type definitions to match.