- Create RollbackCoordinator shared by TransactionService and DistributedTransactionService - Remove unused executeSimpleTransaction() - Split AuditService into AuditLogService (writes) and AuditQueryService (reads) - Create CacheStrategyBase with request coalescing, metrics, and getOrSet pattern - Refactor orders and support cache services to extend CacheStrategyBase
118 lines
3.5 KiB
TypeScript
118 lines
3.5 KiB
TypeScript
import { Injectable } from "@nestjs/common";
|
|
import { CacheService } from "@bff/infra/cache/cache.service.js";
|
|
import { CacheStrategyBase } from "@bff/infra/cache/cache-strategy.base.js";
|
|
import type { CacheBucketMetrics } from "@bff/infra/cache/cache.types.js";
|
|
import type { SupportCaseList, CaseMessageList } from "@customer-portal/domain/support";
|
|
|
|
/**
|
|
* Cache TTL configuration for support cases
|
|
*
|
|
* Unlike orders (which use CDC events), support cases use TTL-based caching
|
|
* because Salesforce Case changes don't trigger platform events in our setup.
|
|
*/
|
|
const CACHE_TTL = {
|
|
/** Case list TTL: 2 minutes - customers checking status periodically */
|
|
CASE_LIST: 120,
|
|
/** Case messages TTL: 1 minute - fresher for active conversations */
|
|
CASE_MESSAGES: 60,
|
|
} as const;
|
|
|
|
interface SupportCacheMetrics {
|
|
caseList: CacheBucketMetrics;
|
|
messages: CacheBucketMetrics;
|
|
invalidations: number;
|
|
}
|
|
|
|
/**
|
|
* Support cases cache service
|
|
*
|
|
* Uses TTL-based caching (not CDC) because:
|
|
* - Cases don't trigger platform events in our Salesforce setup
|
|
* - Customers can refresh to see updates (no real-time requirement)
|
|
* - Short TTLs (1-2 min) ensure reasonable freshness
|
|
*
|
|
* Features:
|
|
* - Request coalescing: Prevents duplicate API calls on cache miss
|
|
* - Write-through invalidation: Cache is cleared after customer adds comment
|
|
* - Metrics tracking: Monitors hits, misses, and invalidations
|
|
*/
|
|
@Injectable()
|
|
export class SupportCacheService extends CacheStrategyBase<"caseList" | "messages"> {
|
|
constructor(cache: CacheService) {
|
|
super(cache, {
|
|
caseList: { ttlSeconds: CACHE_TTL.CASE_LIST },
|
|
messages: { ttlSeconds: CACHE_TTL.CASE_MESSAGES },
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get cached case list for an account
|
|
*/
|
|
async getCaseList(
|
|
sfAccountId: string,
|
|
fetcher: () => Promise<SupportCaseList>
|
|
): Promise<SupportCaseList> {
|
|
const key = this.buildCaseListKey(sfAccountId);
|
|
return this.getOrSet("caseList", key, fetcher);
|
|
}
|
|
|
|
/**
|
|
* Get cached messages for a case
|
|
*/
|
|
async getCaseMessages(
|
|
caseId: string,
|
|
fetcher: () => Promise<CaseMessageList>
|
|
): Promise<CaseMessageList> {
|
|
const key = this.buildMessagesKey(caseId);
|
|
return this.getOrSet("messages", key, fetcher);
|
|
}
|
|
|
|
/**
|
|
* Invalidate case list cache for an account
|
|
* Called after customer creates a new case
|
|
*/
|
|
async invalidateCaseList(sfAccountId: string): Promise<void> {
|
|
const key = this.buildCaseListKey(sfAccountId);
|
|
await this.invalidate(key);
|
|
}
|
|
|
|
/**
|
|
* Invalidate messages cache for a case
|
|
* Called after customer adds a comment
|
|
*/
|
|
async invalidateCaseMessages(caseId: string): Promise<void> {
|
|
const key = this.buildMessagesKey(caseId);
|
|
await this.invalidate(key);
|
|
}
|
|
|
|
/**
|
|
* Invalidate all caches for an account's cases
|
|
* Called after any write operation to ensure fresh data
|
|
*/
|
|
async invalidateAllForAccount(sfAccountId: string, caseId?: string): Promise<void> {
|
|
await this.invalidateCaseList(sfAccountId);
|
|
if (caseId) {
|
|
await this.invalidateCaseMessages(caseId);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get cache metrics for monitoring
|
|
*/
|
|
getMetrics(): SupportCacheMetrics {
|
|
return {
|
|
caseList: this.getBucketMetrics("caseList"),
|
|
messages: this.getBucketMetrics("messages"),
|
|
invalidations: this.getInvalidationCount(),
|
|
};
|
|
}
|
|
|
|
private buildCaseListKey(sfAccountId: string): string {
|
|
return `support:cases:${sfAccountId}`;
|
|
}
|
|
|
|
private buildMessagesKey(caseId: string): string {
|
|
return `support:messages:${caseId}`;
|
|
}
|
|
}
|