11 KiB
Domain & BFF Clean Architecture Refactoring
Overview
Establish clean separation between domain (business logic) and BFF (infrastructure) layers by:
- Removing redundant mapper service wrappers in BFF
- Moving query builders from domain to BFF integration
- Using domain mappers directly in BFF services
- Eliminating unnecessary transformation layers
Current Issues
1. Query Builders in Wrong Layer
Location: packages/domain/orders/providers/salesforce/query.ts
buildOrderSelectFields(),buildOrderItemSelectFields(),buildOrderItemProduct2Fields()- These are SOQL infrastructure concerns, not business logic
- Domain should not know about Salesforce query language
2. Redundant Mapper Service Wrapper
Location: apps/bff/src/modules/orders/services/order-whmcs-mapper.service.ts
- Just wraps
Providers.Whmcs.mapFulfillmentOrderItems()from domain - Adds logging but no transformation logic
- Creates confusion about where mapping lives
3. Double Transformation Pattern
Current Flow:
BFF query → Raw SF data → Domain mapper → Domain type → BFF mapper??? → Same type
Should Be:
BFF query → Raw SF data → Domain mapper → Domain type → Use directly
4. Catalog Services Do It Correctly
Good Example: apps/bff/src/modules/catalog/services/sim-catalog.service.ts
const product = CatalogProviders.Salesforce.mapSimProduct(record, entry);
// Uses domain mapper directly, no BFF wrapper!
Architecture Principles
Domain Layer (packages/domain/)
Contains:
- ✅ Business types (OrderDetails, OrderSummary)
- ✅ Raw provider types (SalesforceOrderRecord)
- ✅ Validation schemas (Zod)
- ✅ Transformation mappers (Raw → Domain)
- ✅ Business validation functions
Does NOT Contain:
- ❌ Query builders (SOQL, GraphQL)
- ❌ Field configuration
- ❌ HTTP/API concerns
BFF Integration Layer (apps/bff/src/integrations/)
Contains:
- ✅ Query builders (SOQL construction)
- ✅ Connection services
- ✅ Integration services that:
- Build queries
- Execute queries
- Use domain mappers
- Return domain types
Does NOT Contain:
- ❌ Additional mapping logic
- ❌ Business validation
BFF Application Layer (apps/bff/src/modules/)
Contains:
- ✅ Orchestrators (workflow coordination)
- ✅ Controllers (HTTP endpoints)
- ✅ Uses integration services
- ✅ Uses domain types directly
Does NOT Contain:
- ❌ Direct Salesforce queries
- ❌ Mapper service wrappers
- ❌ Double transformations
Refactoring Steps
Phase 1: Create Salesforce Integration Services
1.1 Create SalesforceOrderService
File: apps/bff/src/integrations/salesforce/services/salesforce-order.service.ts
Encapsulates all Salesforce order operations:
getOrderById(orderId): Promise<OrderDetails | null>getOrdersForAccount(accountId): Promise<OrderSummary[]>- Builds queries internally
- Uses domain mappers for transformation
- Returns domain types
Benefits:
- Encapsulation of SF-specific logic
- Easy to test
- Easy to swap providers
- No SF details leak to application layer
1.2 Update OrderOrchestrator
- Remove direct
this.sf.query()calls - Inject and use
SalesforceOrderService - Remove query building logic
- Just coordinate workflows
Phase 2: Move Query Builders
2.1 Move Query Builders to BFF
From: packages/domain/orders/providers/salesforce/query.ts
To: apps/bff/src/integrations/salesforce/utils/order-query-builder.ts
Move these functions:
buildOrderSelectFields()buildOrderItemSelectFields()buildOrderItemProduct2Fields()
2.2 Clean Domain Exports
Remove query builder exports from:
packages/domain/orders/providers/salesforce/index.tspackages/domain/orders/providers/index.tspackages/domain/orders/index.ts
Phase 3: Remove Redundant Mapper Services
3.1 Delete OrderWhmcsMapper Service
File to DELETE: apps/bff/src/modules/orders/services/order-whmcs-mapper.service.ts
It only wraps domain mappers - provides no value.
3.2 Update OrderFulfillmentOrchestrator
Replace:
constructor(private orderWhmcsMapper: OrderWhmcsMapper) {}
const result = this.orderWhmcsMapper.mapOrderItemsToWhmcs(items);
With:
import { Providers } from '@customer-portal/domain/orders';
const result = Providers.Whmcs.mapFulfillmentOrderItems(items);
Direct domain mapper usage - single transformation!
3.3 Update orders.module.ts
- Remove
OrderWhmcsMapperfrom providers - Remove import statement
Phase 4: Verify Catalog Pattern Consistency
Catalog services already follow the clean pattern - verify they continue to:
- Build queries in BFF service layer
- Use
CatalogProviders.Salesforce.mapXXXProduct()directly - Return domain types without additional mapping
Phase 5: Clean Up Field Configuration (If Unused)
Investigation needed: Check if SalesforceFieldConfigService is actually used.
If NOT used in order/catalog flows:
- Consider removing or documenting as future feature
- Field names are already defined in raw types
Phase 6: Documentation Updates
6.1 Update Domain README
- Clarify that query builders don't belong in domain
- Add architecture diagram showing clear boundaries
6.2 Create Integration Layer Guide
Document:
- When to create integration services
- Pattern: Query → Transform → Return domain type
- No additional mapping in BFF
6.3 Update ORDERS-ARCHITECTURE-REVIEW.md
- Mark query builders as moved
- Mark mapper wrappers as removed
- Show final clean architecture
File Changes Summary
Files to CREATE
apps/bff/src/integrations/salesforce/services/salesforce-order.service.tsapps/bff/src/integrations/salesforce/utils/order-query-builder.ts
Files to MODIFY
apps/bff/src/modules/orders/services/order-orchestrator.service.ts- Use SalesforceOrderServiceapps/bff/src/modules/orders/services/order-fulfillment-orchestrator.service.ts- Use domain mapper directlyapps/bff/src/modules/orders/orders.module.ts- Update providersapps/bff/src/integrations/salesforce/salesforce.module.ts- Export new servicepackages/domain/orders/providers/salesforce/index.ts- Remove query exportspackages/domain/orders/providers/index.ts- Remove query exportspackages/domain/orders/index.ts- Remove query exports
Files to DELETE
apps/bff/src/modules/orders/services/order-whmcs-mapper.service.tspackages/domain/orders/providers/salesforce/query.ts
Documentation to UPDATE
packages/domain/README.mdORDERS-ARCHITECTURE-REVIEW.md- Create:
docs/BFF-INTEGRATION-PATTERNS.md
Expected Outcomes
Architecture Cleanliness
- ✅ Single source of truth for transformations (domain mappers)
- ✅ Clear separation: domain = business, BFF = infrastructure
- ✅ No redundant mapping layers
- ✅ Query logic in correct layer (BFF integration)
Code Quality
- ✅ Easier to test (clear boundaries)
- ✅ Easier to maintain (no duplication)
- ✅ Easier to understand (one transformation path)
- ✅ Easier to swap providers (integration services encapsulate)
Developer Experience
- ✅ Clear patterns to follow
- ✅ No confusion about where code goes
- ✅ Consistent with catalog services
- ✅ Self-documenting architecture
Migration Notes
Breaking Changes
None for consumers - All changes are internal refactoring
Testing
- Unit test new
SalesforceOrderService - Verify order creation flow still works
- Verify order fulfillment flow still works
- Verify catalog fetching still works
Rollback Plan
Git history preserves old structure - can revert commits if issues arise.
Success Criteria
- Query builders moved to BFF integration layer
OrderWhmcsMapperservice deletedSalesforceOrderServicecreated and usedOrderOrchestratorno longer builds SOQL queriesOrderFulfillmentOrchestratoruses domain mapper directly- Domain exports cleaned (no query builders)
- Documentation updated
- All tests passing (no linting errors)
- Order creation works end-to-end (ready for testing)
- Order fulfillment works end-to-end (ready for testing)
Final Architecture:
┌─────────────────────────────────────────┐
│ Controller (HTTP) │
└──────────────┬──────────────────────────┘
│
┌──────────────▼──────────────────────────┐
│ Orchestrator (Application) │
│ - Coordinates workflows │
│ - Uses Integration Services │
│ - Works with Domain Types │
└──────────────┬──────────────────────────┘
│
┌──────────┴──────────┐
│ │
┌───▼───────────┐ ┌──────▼──────────────┐
│ Domain │ │ Integration │
│ (Business) │ │ (Infrastructure) │
│ │ │ │
│ • Types │ │ • SF OrderService │
│ • Schemas │ │ • Query Builders │
│ • Mappers ────┼──┤ • Connections │
│ • Validators │ │ • Field Mapping │
└───────────────┘ └─────────────────────┘
Flow: Query (BFF) → Raw Data → Domain Mapper → Domain Type → Use Directly
└─ One transformation, no duplication ─┘
To-dos
- Create SalesforceOrderService in BFF integration layer with methods: getOrderById, getOrdersForAccount
- Move query builders from packages/domain/orders/providers/salesforce/query.ts to apps/bff/src/integrations/salesforce/utils/order-query-builder.ts
- Update OrderOrchestrator to use SalesforceOrderService instead of direct SF queries
- Delete redundant OrderWhmcsMapper service wrapper
- Update OrderFulfillmentOrchestrator to use domain Providers.Whmcs mapper directly
- Remove query builder exports from domain package index files
- Update orders.module.ts and salesforce.module.ts with new services
- Verify catalog services follow same clean pattern (already correct)
- Update domain README and architecture documentation with clean patterns
- Test order creation and fulfillment flows end-to-end