refactor: extract Salesforce throttle base guard and rename RealtimeModule

- Create SalesforceThrottleBaseGuard abstract base class (read/write guards are thin subclasses)
- Rename infra RealtimeModule to RealtimePubSubModule to distinguish from RealtimeApiModule
This commit is contained in:
barsa 2026-02-24 11:57:59 +09:00
parent 6e51012d21
commit b00e7aac95
5 changed files with 47 additions and 63 deletions

View File

@ -15,7 +15,7 @@ import { RateLimitModule } from "@bff/core/rate-limiting/index.js";
import { PrismaModule } from "@bff/infra/database/prisma.module.js"; import { PrismaModule } from "@bff/infra/database/prisma.module.js";
import { RedisModule } from "@bff/infra/redis/redis.module.js"; import { RedisModule } from "@bff/infra/redis/redis.module.js";
import { CacheModule } from "@bff/infra/cache/cache.module.js"; import { CacheModule } from "@bff/infra/cache/cache.module.js";
import { RealtimeModule } from "@bff/infra/realtime/realtime.module.js"; import { RealtimePubSubModule } from "@bff/infra/realtime/realtime.module.js";
import { QueueModule } from "@bff/infra/queue/queue.module.js"; import { QueueModule } from "@bff/infra/queue/queue.module.js";
import { AuditModule } from "@bff/infra/audit/audit.module.js"; import { AuditModule } from "@bff/infra/audit/audit.module.js";
import { EmailModule } from "@bff/infra/email/email.module.js"; import { EmailModule } from "@bff/infra/email/email.module.js";
@ -73,7 +73,7 @@ import { HealthModule } from "@bff/modules/health/health.module.js";
PrismaModule, PrismaModule,
RedisModule, RedisModule,
CacheModule, CacheModule,
RealtimeModule, RealtimePubSubModule,
QueueModule, QueueModule,
AuditModule, AuditModule,
EmailModule, EmailModule,

View File

@ -7,4 +7,4 @@ import { RealtimeService } from "./realtime.service.js";
providers: [RealtimePubSubService, RealtimeService], providers: [RealtimePubSubService, RealtimeService],
exports: [RealtimeService], exports: [RealtimeService],
}) })
export class RealtimeModule {} export class RealtimePubSubModule {}

View File

@ -1,33 +1,9 @@
import { Inject, Injectable, HttpException, HttpStatus } from "@nestjs/common"; import { Injectable } from "@nestjs/common";
import type { CanActivate, ExecutionContext } from "@nestjs/common"; import { SalesforceThrottleBaseGuard } from "./salesforce-throttle-base.guard.js";
import type { Request } from "express";
import { Logger } from "nestjs-pino";
import { SalesforceRequestQueueService } from "@bff/infra/queue/services/salesforce-request-queue.service.js";
@Injectable() @Injectable()
export class SalesforceReadThrottleGuard implements CanActivate { export class SalesforceReadThrottleGuard extends SalesforceThrottleBaseGuard {
constructor( protected readonly operationType = "read";
private readonly queue: SalesforceRequestQueueService, protected readonly userMessage =
@Inject(Logger) private readonly logger: Logger "We're experiencing high load right now. Please try again in a moment.";
) {}
canActivate(context: ExecutionContext): boolean {
const state = this.queue.getDegradationState();
if (!state.degraded) {
return true;
}
const request = context.switchToHttp().getRequest<Request>();
this.logger.warn("Throttling Salesforce-backed read due to degraded state", {
path: request?.originalUrl ?? request?.url,
reason: state.reason,
cooldownExpiresAt: state.cooldownExpiresAt,
usagePercent: state.usagePercent,
});
throw new HttpException(
"We're experiencing high load right now. Please try again in a moment.",
HttpStatus.TOO_MANY_REQUESTS
);
}
} }

View File

@ -0,0 +1,32 @@
import { Inject, HttpException, HttpStatus } from "@nestjs/common";
import type { CanActivate, ExecutionContext } from "@nestjs/common";
import type { Request } from "express";
import { Logger } from "nestjs-pino";
import { SalesforceRequestQueueService } from "@bff/infra/queue/services/salesforce-request-queue.service.js";
export abstract class SalesforceThrottleBaseGuard implements CanActivate {
protected abstract readonly operationType: string;
protected abstract readonly userMessage: string;
constructor(
protected readonly queue: SalesforceRequestQueueService,
@Inject(Logger) protected readonly logger: Logger
) {}
canActivate(context: ExecutionContext): boolean {
const state = this.queue.getDegradationState();
if (!state.degraded) {
return true;
}
const request = context.switchToHttp().getRequest<Request>();
this.logger.warn(`Throttling Salesforce-backed ${this.operationType} due to degraded state`, {
path: request?.originalUrl ?? request?.url,
reason: state.reason,
cooldownExpiresAt: state.cooldownExpiresAt,
usagePercent: state.usagePercent,
});
throw new HttpException(this.userMessage, HttpStatus.TOO_MANY_REQUESTS);
}
}

View File

@ -1,33 +1,9 @@
import { Inject, Injectable, HttpException, HttpStatus } from "@nestjs/common"; import { Injectable } from "@nestjs/common";
import type { CanActivate, ExecutionContext } from "@nestjs/common"; import { SalesforceThrottleBaseGuard } from "./salesforce-throttle-base.guard.js";
import type { Request } from "express";
import { Logger } from "nestjs-pino";
import { SalesforceRequestQueueService } from "@bff/infra/queue/services/salesforce-request-queue.service.js";
@Injectable() @Injectable()
export class SalesforceWriteThrottleGuard implements CanActivate { export class SalesforceWriteThrottleGuard extends SalesforceThrottleBaseGuard {
constructor( protected readonly operationType = "write";
private readonly queue: SalesforceRequestQueueService, protected readonly userMessage =
@Inject(Logger) private readonly logger: Logger "We're processing a high volume of requests right now. Please retry shortly.";
) {}
canActivate(context: ExecutionContext): boolean {
const state = this.queue.getDegradationState();
if (!state.degraded) {
return true;
}
const request = context.switchToHttp().getRequest<Request>();
this.logger.warn("Throttling Salesforce-backed write due to degraded state", {
path: request?.originalUrl ?? request?.url,
reason: state.reason,
cooldownExpiresAt: state.cooldownExpiresAt,
usagePercent: state.usagePercent,
});
throw new HttpException(
"We're processing a high volume of requests right now. Please retry shortly.",
HttpStatus.TOO_MANY_REQUESTS
);
}
} }