- Added new environment variables for Salesforce event channels and Change Data Capture (CDC) to improve cache invalidation and event handling. - Updated Salesforce module to include new guards for write operations, enhancing request rate limiting. - Refactored various services to utilize caching for improved performance and reduced API calls, including updates to the Orders and Catalog modules. - Enhanced error handling and logging in Salesforce services to provide better insights during operations. - Improved cache TTL configurations for better memory management and data freshness across catalog and order services.
10 KiB
10 KiB
CDC Event Flow: Customer-Specific vs Global Cache
🎯 The Key Misunderstanding
What CDC Events Actually Contain
// CDC Event from Salesforce
{
"payload": {
"Id": "01t5g000002AbcdEAC", // Product ID
"Name": "Internet Home 1G", // Product Name
"changeType": "UPDATE",
"changedFields": ["Name", "UnitPrice"],
"entityName": "Product2"
},
"replayId": 12345
}
Notice:
- ✅ Contains: Product ID, what changed
- ❌ Does NOT contain: Customer ID, User ID, Account ID
- ❌ Does NOT specify: "For Customer A" or "For Customer B"
CDC events are GLOBAL notifications, not customer-specific!
🔄 Complete Flow: What Happens With CDC
Scenario: Price Change for "Internet Home 1G"
TIME: 10:00 AM
SALESFORCE: Admin changes price $50 → $60
↓
↓ (1 CDC Event sent)
↓
PORTAL CDC SUBSCRIBER receives event:
{
"Id": "01t123...",
"changeType": "UPDATE",
"changedFields": ["UnitPrice"]
}
↓
CACHE INVALIDATION (Global):
Redis: DELETE "catalog:internet:plans"
↓
Cache key deleted from Redis
No cache exists anymore for ANYONE
👥 What Happens to Different Customer Types?
Customer A: Online & Active (viewing website)
10:00:00 AM - Viewing catalog page
↓ Cache hit (old price $50)
10:00:05 AM - CDC event received
↓ Cache deleted
10:00:10 AM - Refreshes page
↓ Cache miss (key deleted)
↓ API call to Salesforce
↓ Fetches new data (price $60)
↓ Stores in cache with 24h TTL
↓ Shows new price $60 ✅
Action taken: Cache miss → API call → Fresh data
Customer B: Online & Idle (logged in but not viewing catalog)
10:00:00 AM - Logged in, viewing dashboard
(Not looking at catalog)
10:00:05 AM - CDC event received
↓ Cache deleted (global)
10:30:00 AM - Clicks "View Plans" for first time
↓ Cache miss (was deleted at 10:00)
↓ API call to Salesforce
↓ Fetches new data (price $60)
↓ Stores in cache
↓ Shows new price $60 ✅
Action taken: Cache miss → API call → Fresh data
Customer C: Offline (not logged in for 7 days)
Day 1 - 9:00 AM - Customer C logs in
↓ Cache miss
↓ API call (fetches old price $50)
↓ Cache populated
Day 1 - 10:00 AM - CDC event (price changed to $60)
↓ Cache deleted
↓ Customer C logs out
Day 2-7: - Customer C offline
- Cache doesn't exist (deleted on Day 1)
- No action needed ✅
Day 8 - 8:00 AM - Customer C logs back in
↓ Clicks "View Plans"
↓ Cache miss (doesn't exist)
↓ API call to Salesforce
↓ Fetches new data (price $60)
↓ Shows new price $60 ✅
Action taken: Nothing during offline period. Fresh fetch on login.
🎯 Key Point: ONE Cache Key for ALL Customers
Your catalog cache structure:
// GLOBAL cache keys (shared by ALL customers)
"catalog:internet:plans" // ← All customers use this
"catalog:sim:plans" // ← All customers use this
"catalog:vpn:plans" // ← All customers use this
// USER-SPECIFIC cache keys (per customer)
"catalog:eligibility:801xxx" // ← Customer A's eligibility
"catalog:eligibility:802xxx" // ← Customer B's eligibility
When Product2 CDC event arrives:
// Invalidates GLOBAL keys (affects everyone)
await cache.delPattern("catalog:internet:*");
await cache.delPattern("catalog:sim:*");
await cache.delPattern("catalog:vpn:*");
// Does NOT invalidate user-specific keys
// "catalog:eligibility:801xxx" stays intact
💡 Why This Works Perfectly
1. Offline Customers Don't Waste Resources ✅
CDC event arrives → Cache deleted
↓
Offline customers:
- Not requesting data (they're offline)
- Not using API calls (they're offline)
- Not consuming memory (cache deleted)
↓
Result: ZERO resources wasted ✅
2. Online Customers Get Fresh Data ✅
CDC event arrives → Cache deleted
↓
Next request (from ANY online customer):
- Cache miss
- 1 API call to Salesforce
- Fresh data stored in cache
↓
Subsequent requests (from ALL online customers):
- Cache hit
- 0 API calls
↓
Result: Fresh data shared by everyone ✅
3. Memory Stays Lean ✅
Before CDC:
Redis: "catalog:internet:plans" = [old data]
Memory: ~500KB
CDC event arrives:
Redis: DELETE "catalog:internet:plans"
Memory: 0 KB ✅
Next customer request:
Redis: "catalog:internet:plans" = [fresh data]
Memory: ~500KB (with 24h TTL)
🔄 Complete Example: 100 Customers
SETUP:
- 100 total customers
- 50 online & active (viewing website)
- 30 online & idle (logged in, not viewing catalog)
- 20 offline (not logged in)
TIME: 10:00 AM - Product price changes in Salesforce
↓
ONE CDC event sent (not 100 events!)
↓
Portal receives event
↓
DELETE "catalog:internet:plans" (one global key)
↓
Cache no longer exists for ANYONE
TIME: 10:01 AM - Customer #37 (online, active) refreshes page
↓
Cache miss (key deleted)
↓
1 API call to Salesforce
↓
Fetches fresh data (new price)
↓
Stores in Redis with 24h TTL
↓
Key: "catalog:internet:plans" = [fresh data]
TIME: 10:02 AM - Customer #42 (online, active) refreshes page
↓
Cache HIT (Customer #37 populated it)
↓
0 API calls ✅
↓
Shows fresh data
TIME: 10:03 AM - Customers #1-20 (online, active) view catalog
↓
All cache HITs
↓
0 API calls ✅
TIME: 10:30 AM - Customer #55 (was idle, now viewing catalog)
↓
Cache HIT (still fresh from 10:01 AM)
↓
0 API calls ✅
OFFLINE CUSTOMERS (#81-100):
↓
Not requesting anything (offline)
↓
0 API calls ✅
↓
When they log in later:
- Cache might exist (if populated by others)
- OR Cache might be expired (24h TTL)
- Either way: Fresh data
RESULT:
CDC event: 1 event for 100 customers
API calls: 1 call (Customer #37)
Cache hits: 99 other customers shared the result
Offline customers: 0 impact, 0 waste
🎯 Direct Answers to Your Questions
Q1: "We received CDC for a customer that's offline, what do we do?"
Answer: CDC is NOT "for a customer" - it's a GLOBAL notification!
CDC Event: "Product X changed"
↓
Action: Delete global cache key
↓
Offline customer: Does nothing (not requesting data)
↓
When they login later: Fetches fresh data
Q2: "What do we do for existing customer?"
Answer: Same action - delete global cache!
CDC Event: "Product X changed"
↓
Action: Delete global cache key
↓
Online customer: Next request is cache miss
↓
Fetches fresh data from Salesforce
↓
Stores in cache for everyone
🔍 Only USER-SPECIFIC Data Has Per-Customer Logic
Global Cache (CDC invalidates for everyone):
// Products - same for all customers
"catalog:internet:plans"
// Prices - same for all customers
"catalog:sim:plans"
// Addons - same for all customers
"catalog:vpn:plans"
User-Specific Cache (CDC invalidates per customer):
// Eligibility - different per customer
"catalog:eligibility:801xxx" ← Customer A
"catalog:eligibility:802xxx" ← Customer B
// Orders - different per customer
"orders:account:801xxx" ← Customer A's orders
"orders:account:802xxx" ← Customer B's orders
Account eligibility CDC:
{
"payload": {
"AccountId": "801xxx", // ← Specific customer!
"Internet_Eligibility__c": "Home 10G"
}
}
Action:
// Only invalidate THAT customer's eligibility
await cache.del("catalog:eligibility:801xxx");
// Other customers' eligibility stays cached ✅
📊 Summary Table
| Cache Type | CDC Event | Offline Customer | Online Customer |
|---|---|---|---|
| Global Catalog | Product2 changed | Delete global cache. Customer offline, no impact. When logs in: fresh fetch | Delete global cache. Next request: cache miss, fetch fresh |
| User Eligibility | Account X changed | Delete cache for Customer X only. Other customers unaffected | Delete cache for Customer X only. Next request: fresh fetch |
| Orders | Order X changed | Delete cache for Order X & Account. Customer offline, no impact | Delete cache for Order X & Account. Next request: fresh fetch |
🎓 The Elegance of This Design
Why it works:
- CDC is a notification system, not a data distribution system
- Cache is deleted, not updated → Zero stale data
- Global cache shared by all → Maximum efficiency
- Lazy loading → Only fetch when actually requested
- Offline users invisible → No special handling needed
Result:
- ✅ Simple logic (no tracking of online/offline)
- ✅ Correct behavior (always fresh data)
- ✅ Efficient (minimal API calls)
- ✅ Memory efficient (deleted cache = 0 bytes)
🚀 Conclusion
When CDC arrives:
- Delete the global cache key
- Done. That's it.
Offline customers:
- Not requesting data → No impact
- No API calls → No cost
- No memory used → Efficient
Online customers:
- Next request → Cache miss
- 1 API call → Fresh data
- Other customers → Cache hit
You don't need to:
- ❌ Track who's online/offline
- ❌ Check customer status
- ❌ Store data per customer (for global catalog)
- ❌ Do anything special
Just:
- ✅ Delete cache when CDC arrives
- ✅ Let customers fetch on-demand
- ✅ Share cached results globally
Simple, correct, efficient! 🎉