# CDC-Only Order Provisioning (Alternative Approach) ## Overview This document shows how to use ONLY CDC (OrderCdcSubscriber) for both cache invalidation AND order provisioning, eliminating the need for Platform Events. ## Modified OrderCdcSubscriber ```typescript import { Injectable, Inject, OnModuleInit, OnModuleDestroy } from "@nestjs/common"; import { ConfigService } from "@nestjs/config"; import { Logger } from "nestjs-pino"; import PubSubApiClientPkg from "salesforce-pubsub-api-client"; import { SalesforceConnection } from "../services/salesforce-connection.service"; import { OrdersCacheService } from "@bff/modules/orders/services/orders-cache.service"; import { ProvisioningQueueService } from "@bff/modules/orders/queue/provisioning.queue"; @Injectable() export class OrderCdcSubscriber implements OnModuleInit, OnModuleDestroy { // ... (same client setup as before) private readonly PROVISION_TRIGGER_STATUSES = new Set(["Approved", "Reactivate"]); constructor( private readonly config: ConfigService, private readonly sfConnection: SalesforceConnection, private readonly ordersCache: OrdersCacheService, private readonly provisioningQueue: ProvisioningQueueService, // ← Add this @Inject(Logger) private readonly logger: Logger ) {} private async handleOrderEvent(data: unknown): Promise { const payload = this.extractPayload(data); const changedFields = this.extractChangedFields(payload); const orderId = this.extractStringField(payload, ["Id"]); const accountId = this.extractStringField(payload, ["AccountId"]); if (!orderId) { this.logger.warn("Order CDC event missing Order ID; skipping"); return; } // 1. CHECK FOR PROVISIONING TRIGGER if (changedFields.has("Activation_Status__c")) { await this.handleActivationStatusChange(payload, orderId); } // 2. CACHE INVALIDATION (existing logic) const hasCustomerFacingChange = this.hasCustomerFacingChanges(changedFields); if (hasCustomerFacingChange) { this.logger.log("Order CDC event with customer-facing changes, invalidating cache", { orderId, accountId, changedFields: Array.from(changedFields), }); await this.ordersCache.invalidateOrder(orderId); if (accountId) { await this.ordersCache.invalidateAccountOrders(accountId); } } else { this.logger.debug("Order CDC event contains only internal field changes; skipping invalidation", { orderId, changedFields: Array.from(changedFields), }); } } /** * Handle Status field changes and trigger provisioning if needed */ private async handleActivationStatusChange( payload: Record, orderId: string ): Promise { const activationStatus = this.extractStringField(payload, ["Activation_Status__c"]); const status = this.extractStringField(payload, ["Status"]); if (activationStatus !== "Activating") { this.logger.debug("Activation status changed but not to Activating", { orderId, activationStatus, }); return; } if (status && !this.PROVISION_TRIGGER_STATUSES.has(status)) { this.logger.debug("Activation set to Activating but order status isn't Approved/Reactivate", { orderId, activationStatus, status, }); return; } const whmcsOrderId = this.extractStringField(payload, ["WHMCS_Order_ID__c"]); if (whmcsOrderId) { this.logger.log("Order already has WHMCS Order ID, skipping provisioning", { orderId, whmcsOrderId, }); return; } this.logger.log("Activation status moved to Activating, enqueuing fulfillment", { orderId, activationStatus, status, }); try { await this.provisioningQueue.enqueue({ sfOrderId: orderId, idempotencyKey: `cdc-activation-${Date.now()}-${orderId}`, correlationId: `cdc-${orderId}`, }); this.logger.log("Successfully enqueued provisioning job from activation change", { orderId, activationStatus, status, }); } catch (error) { this.logger.error("Failed to enqueue provisioning job from activation change", { orderId, error: error instanceof Error ? error.message : String(error), }); } } // ... (rest of the existing methods) } ``` ## Comparison ### Before (Platform Event + CDC): ``` Salesforce Flow: - Order Status = "Approved" - Publish Order_Fulfilment_Requested__e ↓ Portal (SalesforcePubSubSubscriber): - Receives Platform Event - Enqueues provisioning job Portal (OrderCdcSubscriber): - Receives OrderChangeEvent - Invalidates cache ``` ### After (CDC Only): ``` Salesforce: - Order Status = "Approved" (Flow sets Activation_Status__c = "Activating") ↓ Portal (OrderCdcSubscriber): - Receives OrderChangeEvent - Checks: Activation_Status__c changed to "Activating"? - Yes → Enqueues provisioning job - Also → Invalidates cache (if customer-facing) ``` ## Benefits of CDC-Only 1. ✅ **Simpler Salesforce Setup** - No Platform Event definition needed - No Flow to publish event - Just enable CDC on Order object 2. ✅ **Single Mechanism** - One subscriber handles everything - Fewer moving parts - Easier to understand 3. ✅ **Automatic** - Any Flow that sets `Activation_Status__c = "Activating"` triggers provisioning - No manual Flow maintenance ## Drawbacks of CDC-Only 1. ❌ **Less Explicit Control** - No clear "provision this order" signal - Inferred from field changes - Harder to add custom context 2. ❌ **More Guards Needed** - Must check: Not already provisioning - Must check: WHMCS Order ID doesn't exist - Must check: Activation Status - More defensive coding 3. ❌ **Business Logic in Portal** - Portal must know when to provision - Salesforce can't pre-validate - Logic duplicated 4. ❌ **No Custom Metadata** - Can't include: RequestedBy, Priority, etc. - Limited to Order fields - Less context for debugging ## When to Use CDC-Only ✅ Use CDC-Only if: - Simple approval workflow (Draft → Approved → Completed) - No complex business validation needed - Want simplest possible setup - Okay with less control ❌ Stick with Platform Events if: - Complex approval workflows - Need custom context/metadata - Multiple trigger conditions - Business logic should stay in Salesforce ## Migration Steps If you decide to switch to CDC-Only: 1. **Update OrderCdcSubscriber** - Add `ProvisioningQueueService` dependency - Add `handleActivationStatusChange()` method - Add guards for idempotency 2. **Remove SalesforcePubSubSubscriber** (optional) - Or keep it for other Platform Events - Just don't subscribe to Order_Fulfilment_Requested__e 3. **Remove Salesforce Flow** - Delete Flow that publishes Order_Fulfilment_Requested__e - Or disable it 4. **Test Thoroughly** - Test: Activation_Status__c → "Activating" triggers provisioning - Test: Already provisioned orders don't re-trigger - Test: Cancelled orders don't trigger - Test: Cache invalidation still works 5. **Update .env** ```bash # Can remove (if not using other Platform Events) # SF_PROVISION_EVENT_CHANNEL=/event/Order_Fulfilment_Requested__e # Keep these (still needed for CDC) SF_ORDER_CDC_CHANNEL=/data/OrderChangeEvent SF_ORDER_ITEM_CDC_CHANNEL=/data/OrderItemChangeEvent ``` ## Recommendation **For your use case, I recommend keeping the Platform Event approach:** Reasons: 1. You have complex approval workflows 2. You want explicit control over provisioning 3. Salesforce handles business validation 4. Clear separation of concerns: - Platform Event = "Do something" (provisioning) - CDC = "Something changed" (cache invalidation) **The dual-mechanism approach is more robust for production systems.**