Assist_Design/apps/bff/src/modules/support/support-cache.service.ts
barsa 34abe1981f refactor: infrastructure consolidation
- 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
2026-02-24 11:58:02 +09:00

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}`;
}
}