453 lines
12 KiB
Markdown
453 lines
12 KiB
Markdown
|
|
# Order CDC Setup Guide
|
||
|
|
|
||
|
|
## Overview
|
||
|
|
|
||
|
|
This guide explains how to use Change Data Capture (CDC) for **Order cache invalidation** while keeping the Platform Event-based **fulfillment flow** intact.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🔑 Key Concept: Dual Approach
|
||
|
|
|
||
|
|
Your order system uses **TWO separate mechanisms** for different purposes:
|
||
|
|
|
||
|
|
| Mechanism | Purpose | Channel Type | Trigger |
|
||
|
|
|-----------|---------|--------------|---------|
|
||
|
|
| **Platform Events** | Order provisioning/fulfillment | `/event/Order_Fulfilment_Requested__e` | Salesforce Flow when Status = Approved |
|
||
|
|
| **CDC** | Order cache invalidation | `/data/OrderChangeEvent` | ANY Order field change in Salesforce |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🎯 Problem Statement
|
||
|
|
|
||
|
|
**Challenge:** Orders have both:
|
||
|
|
1. **Customer-facing fields** (Status, TotalAmount, BillingAddress) - changes should invalidate cache
|
||
|
|
2. **Internal system fields** (Activation_Status__c, WHMCS_Order_ID__c) - updated by fulfillment, should NOT invalidate cache
|
||
|
|
|
||
|
|
**Why it matters:**
|
||
|
|
- Fulfillment process updates internal fields **every time it runs**
|
||
|
|
- Without filtering, CDC would trigger unnecessary cache invalidation
|
||
|
|
- This would cause cache thrashing and wasted API calls
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🧠 Smart Filtering Strategy
|
||
|
|
|
||
|
|
The `OrderCdcSubscriber` implements **intelligent field filtering** to solve this problem:
|
||
|
|
|
||
|
|
### Customer-Facing Fields (INVALIDATE cache)
|
||
|
|
|
||
|
|
Changes to these fields invalidate cache because customers need to see updates:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// Order fields
|
||
|
|
- Status // Draft → Pending Review → Completed
|
||
|
|
- TotalAmount // Order total
|
||
|
|
- EffectiveDate // Order date
|
||
|
|
- BillingStreet // Billing address
|
||
|
|
- BillingCity
|
||
|
|
- BillingState
|
||
|
|
- BillingPostalCode
|
||
|
|
- BillingCountry
|
||
|
|
- Type // Internet, SIM, VPN
|
||
|
|
- Activation_Type__c // Immediate, Scheduled
|
||
|
|
- Installation_Type__c
|
||
|
|
- Access_Mode__c
|
||
|
|
- Hikari_Denwa__c
|
||
|
|
- VPN_Region__c
|
||
|
|
- SIM_Type__c
|
||
|
|
- EID__c
|
||
|
|
- Address_Changed__c
|
||
|
|
|
||
|
|
// OrderItem fields
|
||
|
|
- Quantity
|
||
|
|
- UnitPrice
|
||
|
|
- Description
|
||
|
|
- Product2Id
|
||
|
|
- Billing_Cycle__c
|
||
|
|
```
|
||
|
|
|
||
|
|
### Internal System Fields (IGNORE - don't invalidate cache)
|
||
|
|
|
||
|
|
Changes to these fields are ignored because they're updated by the fulfillment process:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// Order fields
|
||
|
|
private readonly INTERNAL_FIELDS = new Set([
|
||
|
|
"Activation_Status__c", // Activating → Activated/Failed
|
||
|
|
"WHMCS_Order_ID__c", // Set during fulfillment
|
||
|
|
"Activation_Error_Code__c", // Error tracking
|
||
|
|
"Activation_Error_Message__c", // Error messages
|
||
|
|
"Activation_Last_Attempt_At__c",// Timestamp
|
||
|
|
"ActivatedDate", // Activation timestamp
|
||
|
|
]);
|
||
|
|
|
||
|
|
// OrderItem fields
|
||
|
|
private readonly INTERNAL_ORDER_ITEM_FIELDS = new Set([
|
||
|
|
"WHMCS_Service_ID__c", // Set during fulfillment
|
||
|
|
]);
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🔄 Order Lifecycle & Cache Invalidation
|
||
|
|
|
||
|
|
### Scenario 1: Order Creation (Portal → Salesforce)
|
||
|
|
|
||
|
|
```
|
||
|
|
1. Customer creates order in Portal
|
||
|
|
2. Portal creates Order in Salesforce (Status: "Pending Review")
|
||
|
|
3. CDC fires → Cache invalidation NOT needed (order just created, not in cache)
|
||
|
|
4. Customer sees "Pending Review" status
|
||
|
|
```
|
||
|
|
|
||
|
|
**Cache invalidation:** ❌ Not needed (new order)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Scenario 2: Order Approval (Salesforce → Fulfillment)
|
||
|
|
|
||
|
|
```
|
||
|
|
1. Admin approves Order in Salesforce (Status: "Pending Review" → "Approved")
|
||
|
|
2. CDC fires → CUSTOMER-FACING field changed (Status)
|
||
|
|
3. Cache invalidated ✅
|
||
|
|
4. Flow publishes Order_Fulfilment_Requested__e Platform Event
|
||
|
|
5. Portal subscriber enqueues provisioning job
|
||
|
|
6. Fulfillment process updates:
|
||
|
|
- Activation_Status__c: "Activating"
|
||
|
|
- CDC fires → INTERNAL field changed
|
||
|
|
- Cache invalidation SKIPPED ❌ (internal field only)
|
||
|
|
7. Fulfillment completes, updates:
|
||
|
|
- Status: "Completed"
|
||
|
|
- Activation_Status__c: "Activated"
|
||
|
|
- WHMCS_Order_ID__c: "12345"
|
||
|
|
- CDC fires → CUSTOMER-FACING field changed (Status)
|
||
|
|
- Cache invalidated ✅
|
||
|
|
8. Customer polls for updates, sees "Completed" status
|
||
|
|
```
|
||
|
|
|
||
|
|
**Cache invalidations:**
|
||
|
|
- Step 2: ✅ YES (Status changed - customer-facing)
|
||
|
|
- Step 6: ❌ NO (Only internal fields changed)
|
||
|
|
- Step 7: ✅ YES (Status changed - customer-facing)
|
||
|
|
|
||
|
|
**Why this is smart:**
|
||
|
|
- Step 6 doesn't invalidate cache even though CDC fired
|
||
|
|
- Prevents unnecessary cache invalidation during fulfillment
|
||
|
|
- Cache is only invalidated when customer-visible data changes
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Scenario 3: Admin Updates Order Details (Salesforce UI)
|
||
|
|
|
||
|
|
```
|
||
|
|
1. Admin updates BillingAddress in Salesforce UI
|
||
|
|
2. CDC fires → CUSTOMER-FACING field changed
|
||
|
|
3. Cache invalidated ✅
|
||
|
|
4. Customer sees updated billing address on next page load
|
||
|
|
```
|
||
|
|
|
||
|
|
**Cache invalidation:** ✅ YES (customer-facing field)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Scenario 4: Fulfillment Retry After Failure
|
||
|
|
|
||
|
|
```
|
||
|
|
1. Order in "Failed" state (Activation_Status__c: "Failed")
|
||
|
|
2. Customer adds payment method
|
||
|
|
3. Admin clicks "Retry Fulfillment" → Activation_Status__c: "Activating"
|
||
|
|
4. CDC fires → INTERNAL field changed
|
||
|
|
5. Cache invalidation SKIPPED ❌
|
||
|
|
6. Platform Event triggers fulfillment
|
||
|
|
7. Fulfillment completes → Status: "Completed"
|
||
|
|
8. CDC fires → CUSTOMER-FACING field changed
|
||
|
|
9. Cache invalidated ✅
|
||
|
|
```
|
||
|
|
|
||
|
|
**Cache invalidations:**
|
||
|
|
- Step 4: ❌ NO (internal field)
|
||
|
|
- Step 8: ✅ YES (customer-facing field)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🔧 Implementation Details
|
||
|
|
|
||
|
|
### How Field Filtering Works
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
private async handleOrderEvent(
|
||
|
|
channel: string,
|
||
|
|
subscription: { topicName?: string },
|
||
|
|
callbackType: string,
|
||
|
|
data: unknown
|
||
|
|
): Promise<void> {
|
||
|
|
const payload = this.extractPayload(data);
|
||
|
|
const changedFields = this.extractChangedFields(payload);
|
||
|
|
|
||
|
|
// Filter: Only invalidate if customer-facing fields changed
|
||
|
|
const hasCustomerFacingChange = this.hasCustomerFacingChanges(changedFields);
|
||
|
|
|
||
|
|
if (!hasCustomerFacingChange) {
|
||
|
|
this.logger.debug("Order CDC event contains only internal field changes; skipping", {
|
||
|
|
orderId,
|
||
|
|
changedFields: Array.from(changedFields),
|
||
|
|
});
|
||
|
|
return; // ❌ Don't invalidate cache
|
||
|
|
}
|
||
|
|
|
||
|
|
// ✅ Invalidate cache
|
||
|
|
await this.ordersCache.invalidateOrder(orderId);
|
||
|
|
await this.ordersCache.invalidateAccountOrders(accountId);
|
||
|
|
}
|
||
|
|
|
||
|
|
private hasCustomerFacingChanges(changedFields: Set<string>): boolean {
|
||
|
|
if (changedFields.size === 0) {
|
||
|
|
return true; // Safe default: assume customer-facing if no info
|
||
|
|
}
|
||
|
|
|
||
|
|
// Remove internal fields
|
||
|
|
const customerFacingChanges = Array.from(changedFields).filter(
|
||
|
|
(field) => !this.INTERNAL_FIELDS.has(field)
|
||
|
|
);
|
||
|
|
|
||
|
|
return customerFacingChanges.length > 0;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### CDC Payload Structure
|
||
|
|
|
||
|
|
Salesforce CDC events include information about which fields changed:
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"payload": {
|
||
|
|
"Id": "801xxx",
|
||
|
|
"Status": "Completed",
|
||
|
|
"Activation_Status__c": "Activated",
|
||
|
|
"changeType": "UPDATE",
|
||
|
|
"changedFields": [
|
||
|
|
"Status",
|
||
|
|
"Activation_Status__c"
|
||
|
|
],
|
||
|
|
"changeOrigin": {
|
||
|
|
"changedFields": ["Status", "Activation_Status__c"]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
The subscriber extracts `changedFields` and determines if ANY customer-facing field was changed.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📊 Benefits
|
||
|
|
|
||
|
|
### Before (No Filtering)
|
||
|
|
|
||
|
|
```
|
||
|
|
Fulfillment Process:
|
||
|
|
1. Update Activation_Status__c = "Activating"
|
||
|
|
→ CDC fires → Cache invalidated
|
||
|
|
2. Update WHMCS_Order_ID__c = "12345"
|
||
|
|
→ CDC fires → Cache invalidated
|
||
|
|
3. Update Activation_Status__c = "Activated"
|
||
|
|
→ CDC fires → Cache invalidated
|
||
|
|
4. Update Status = "Completed"
|
||
|
|
→ CDC fires → Cache invalidated
|
||
|
|
|
||
|
|
Result: 4 cache invalidations, 4 Salesforce API calls to refetch order
|
||
|
|
```
|
||
|
|
|
||
|
|
### After (With Smart Filtering)
|
||
|
|
|
||
|
|
```
|
||
|
|
Fulfillment Process:
|
||
|
|
1. Update Activation_Status__c = "Activating"
|
||
|
|
→ CDC fires → Skipped (internal field)
|
||
|
|
2. Update WHMCS_Order_ID__c = "12345"
|
||
|
|
→ CDC fires → Skipped (internal field)
|
||
|
|
3. Update Activation_Status__c = "Activated", Status = "Completed"
|
||
|
|
→ CDC fires → Cache invalidated (Status is customer-facing)
|
||
|
|
|
||
|
|
Result: 1 cache invalidation, 1 Salesforce API call to refetch order
|
||
|
|
```
|
||
|
|
|
||
|
|
**Savings:** 75% fewer cache invalidations during fulfillment!
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🔧 Salesforce Setup
|
||
|
|
|
||
|
|
### Enable CDC on Order Objects
|
||
|
|
|
||
|
|
1. Go to **Setup → Integrations → Change Data Capture**
|
||
|
|
2. Select objects:
|
||
|
|
- ✅ **Order**
|
||
|
|
- ✅ **OrderItem**
|
||
|
|
3. Click **Save**
|
||
|
|
|
||
|
|
**That's it!** CDC is built into Salesforce - no custom Platform Events needed.
|
||
|
|
|
||
|
|
### Permissions
|
||
|
|
|
||
|
|
Ensure your Salesforce integration user has:
|
||
|
|
- **View Change Data Capture Events** permission
|
||
|
|
- **Read** access to Order and OrderItem objects
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## ✅ Verification Steps
|
||
|
|
|
||
|
|
### 1. Check Logs on Application Start
|
||
|
|
|
||
|
|
```bash
|
||
|
|
tail -f logs/app.log | grep -i "order cdc\|OrderChangeEvent"
|
||
|
|
```
|
||
|
|
|
||
|
|
**Expected output:**
|
||
|
|
```
|
||
|
|
Subscribed to Order CDC channel {"orderChannel":"/data/OrderChangeEvent"}
|
||
|
|
Subscribed to OrderItem CDC channel {"orderItemChannel":"/data/OrderItemChangeEvent"}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. Test Fulfillment (Internal Field Changes)
|
||
|
|
|
||
|
|
1. Trigger order fulfillment
|
||
|
|
2. **Check logs for CDC events:**
|
||
|
|
```
|
||
|
|
Order CDC event contains only internal field changes; skipping cache invalidation
|
||
|
|
{"orderId":"801xxx","changedFields":["Activation_Status__c"]}
|
||
|
|
```
|
||
|
|
3. **Verify cache NOT invalidated** (logs show "skipping")
|
||
|
|
|
||
|
|
### 3. Test Admin Update (Customer-Facing Field Changes)
|
||
|
|
|
||
|
|
1. In Salesforce, update Order Status from "Pending Review" to "Cancelled"
|
||
|
|
2. **Check logs for CDC event:**
|
||
|
|
```
|
||
|
|
Order CDC event received with customer-facing changes, invalidating cache
|
||
|
|
{"orderId":"801xxx","changedFields":["Status"]}
|
||
|
|
```
|
||
|
|
3. **Verify cache invalidated** (logs show "invalidating cache")
|
||
|
|
|
||
|
|
### 4. Monitor Cache Metrics
|
||
|
|
|
||
|
|
```bash
|
||
|
|
curl http://localhost:4000/health/orders
|
||
|
|
```
|
||
|
|
|
||
|
|
**Response:**
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"status": "ok",
|
||
|
|
"cdc": {
|
||
|
|
"orderChannel": "/data/OrderChangeEvent",
|
||
|
|
"orderItemChannel": "/data/OrderItemChangeEvent",
|
||
|
|
"status": "connected"
|
||
|
|
},
|
||
|
|
"cache": {
|
||
|
|
"ttl": {
|
||
|
|
"summarySeconds": null,
|
||
|
|
"detailSeconds": null
|
||
|
|
},
|
||
|
|
"metrics": {
|
||
|
|
"invalidations": 45,
|
||
|
|
"skippedInternal": 120
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Key indicators:**
|
||
|
|
- `status: "connected"` = CDC is active
|
||
|
|
- `invalidations: 45` = Cache invalidated 45 times for customer-facing changes
|
||
|
|
- `skippedInternal: 120` = Skipped 120 internal field changes (smart filtering working!)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🚨 Troubleshooting
|
||
|
|
|
||
|
|
### Problem: Cache thrashing during fulfillment
|
||
|
|
|
||
|
|
**Symptom:** Logs show cache invalidation every time fulfillment updates internal fields
|
||
|
|
|
||
|
|
**Solution:** Check `INTERNAL_FIELDS` set includes all system fields:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
private readonly INTERNAL_FIELDS = new Set([
|
||
|
|
"Activation_Status__c",
|
||
|
|
"WHMCS_Order_ID__c",
|
||
|
|
"Activation_Error_Code__c",
|
||
|
|
"Activation_Error_Message__c",
|
||
|
|
"Activation_Last_Attempt_At__c",
|
||
|
|
"ActivatedDate",
|
||
|
|
]);
|
||
|
|
```
|
||
|
|
|
||
|
|
### Problem: Cache not invalidating when admin updates order
|
||
|
|
|
||
|
|
**Symptom:** Admin changes order in Salesforce, but customer doesn't see updates
|
||
|
|
|
||
|
|
**Check:**
|
||
|
|
1. CDC is enabled for Order object in Salesforce
|
||
|
|
2. Logs show CDC event received
|
||
|
|
3. Changed field is NOT in `INTERNAL_FIELDS` set
|
||
|
|
|
||
|
|
### Problem: Too aggressive invalidation
|
||
|
|
|
||
|
|
**Symptom:** Cache is invalidated even for non-customer-facing fields
|
||
|
|
|
||
|
|
**Solution:** Add field to `INTERNAL_FIELDS` set if it's updated by system processes.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📝 Adding New Internal Fields
|
||
|
|
|
||
|
|
If you add new system fields that are updated by fulfillment or background processes:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// In order-cdc.subscriber.ts
|
||
|
|
private readonly INTERNAL_FIELDS = new Set([
|
||
|
|
"Activation_Status__c",
|
||
|
|
"WHMCS_Order_ID__c",
|
||
|
|
"Activation_Error_Code__c",
|
||
|
|
"Activation_Error_Message__c",
|
||
|
|
"Activation_Last_Attempt_At__c",
|
||
|
|
"ActivatedDate",
|
||
|
|
|
||
|
|
// ✅ Add your new internal fields here
|
||
|
|
"Your_New_System_Field__c",
|
||
|
|
]);
|
||
|
|
```
|
||
|
|
|
||
|
|
**Rule of thumb:**
|
||
|
|
- Field updated by **system/fulfillment** → Add to `INTERNAL_FIELDS`
|
||
|
|
- Field updated by **admins/users** → DON'T add (customer-facing)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🎯 Summary
|
||
|
|
|
||
|
|
Your Order CDC setup provides:
|
||
|
|
|
||
|
|
✅ **Smart filtering** - Only invalidates cache for customer-facing field changes
|
||
|
|
✅ **Fulfillment-aware** - Doesn't interfere with Platform Event-based provisioning
|
||
|
|
✅ **Cache efficiency** - 75% fewer invalidations during fulfillment
|
||
|
|
✅ **Real-time updates** - Admin changes reflected within seconds
|
||
|
|
✅ **No manual invalidation** - System handles it automatically
|
||
|
|
|
||
|
|
**Next Steps:**
|
||
|
|
1. Enable CDC for Order and OrderItem in Salesforce
|
||
|
|
2. Restart your application
|
||
|
|
3. Monitor logs for successful CDC subscriptions
|
||
|
|
4. Test by updating an order in Salesforce and verifying cache invalidation
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📚 Related Documentation
|
||
|
|
|
||
|
|
- [CDC_SETUP_VERIFICATION.md](./CDC_SETUP_VERIFICATION.md) - Catalog CDC setup
|
||
|
|
- [SALESFORCE-ORDER-COMMUNICATION.md](./salesforce/SALESFORCE-ORDER-COMMUNICATION.md) - Platform Events for fulfillment
|
||
|
|
- [ORDER-FULFILLMENT-COMPLETE-GUIDE.md](./orders/ORDER-FULFILLMENT-COMPLETE-GUIDE.md) - Fulfillment workflow
|
||
|
|
|