모든 멀티테넌트 SaaS 백엔드는 같은 운영 빌딩 블록들이 필요합니다. 이를 직접 구현하면 수 주가 걸리고 미묘한 버그가 생깁니다. nestarc는 이 문제를 한 번에, 올바르게 해결합니다.
테넌트 격리
쿼리 하나 잘못 쓰면 고객 데이터가 다른 테넌트에 노출됩니다.
PostgreSQL RLS가 데이터베이스 수준에서 격리를 보장합니다.
감사 추적
모든 쓰기 작업에 수동으로 로그를 남기는 건 번거롭고 빠뜨리기 쉽습니다.
Prisma 확장이 CUD를 자동 추적하고 before/after diff를 기록합니다.
피처 플래그
외부 플래그 서비스는 지연, 비용, 새로운 의존성을 추가합니다.
DB 기반 플래그로 테넌트 오버라이드와 퍼센트 롤아웃을 지원합니다.
소프트 딜리트
deletedAt만으로는 유니크 제약 조건이 깨지고 삭제된 레코드가 노출됩니다.
캐스케이드, 복원, 쿼리 필터링을 갖춘 Prisma 확장으로 해결합니다.
페이지네이션
커서 + 오프셋에 필터까지 구현하면 보일러플레이트가 넘칩니다.
12가지 필터 연산자, 정렬, Swagger 문서를 즉시 사용할 수 있습니다.
응답 표준화
엔드포인트마다 다른 API 응답 형식은 프론트엔드 팀을 힘들게 합니다.
에러 코드, 페이지네이션, i18n이 포함된 자동 래핑 응답을 제공합니다.
멱등성
네트워크 재시도는 중복 결제, 중복 주문, 손상된 상태를 만들 수 있습니다.
IETF 스타일 Idempotency-Key 처리와 응답 재생으로 중복 실행을 막습니다.
트랜잭션 outbox
DB 쓰기와 이벤트 발행이 어긋나면 이벤트가 유실되거나 중복될 수 있습니다.
Prisma 네이티브 outbox가 polling, SKIP LOCKED, backoff 재시도를 처리합니다.
Webhook 전달
신뢰할 수 있는 outbound webhook에는 재시도, 서명, 회로 차단, 감사 추적이 필요합니다.
HMAC 서명, exponential backoff, circuit breaker, 전송 로그를 제공합니다.
API 키
키 해싱, prefix, rotation을 직접 만들면 작은 버그 하나가 자격 증명 유출로 이어집니다.
SHA-256 + versioned pepper, Stripe 스타일 prefix, test/live 격리를 제공합니다.
개인정보 권리 요청
GDPR/CCPA export와 erase 요청은 세금, 감사, 법적 보존 요구와 충돌합니다.
엔티티별 delete/anonymize/retain 정책, legal basis 추적, outbox fan-out을 지원합니다.
백그라운드 작업
한 테넌트의 과도한 backlog가 일반 FIFO 큐에서 다른 테넌트의 작업을 굶길 수 있습니다.
최소 share를 보장하는 weighted tenant-fair scheduler와 BullMQ 백엔드를 제공합니다.
nestarc 없이
typescript// 50개 이상의 서비스에 흩어져 있고, 빠뜨리기 쉽고, 감사하기 어렵습니다
async updateUser(id: string, dto: UpdateUserDto) {
const before = await this.prisma.user.findUnique({ where: { id } });
await this.prisma.$executeRaw`SELECT set_config('app.current_tenant', ${tenantId}, true)`;
const after = await this.prisma.user.update({ where: { id, deletedAt: null }, data: dto });
await this.auditService.log({ action: 'user.update', before, after });
return { success: true, data: after, timestamp: new Date() };
}
nestarc와 함께
typescript// 테넌트 격리, 감사 로그, 소프트 딜리트 필터링, 응답 래핑이
// Prisma 확장과 NestJS 인터셉터에 의해 자동으로 처리됩니다.
async updateUser(id: string, dto: UpdateUserDto) {
return this.prisma.user.update({ where: { id }, data: dto });
}