nest, node js기반 배치서버 개발

2025. 11. 3. 18:28NodeJs

반응형

좋아요 👍
아래는 NowSwing과 같은 NestJS/Node.js 환경에서 바로 사용할 수 있는
Node 기반 배치 서버 구조 + 실행 예시 코드입니다.

이 구조는 다음을 전제로 설계되어 있습니다 👇

  • 예약/결제 시스템 백엔드가 NestJS로 되어 있음
  • DB는 Prisma(MySQL) 기반
  • 예약 만료, 결제 상태 동기화, 알림 발송 같은 Job을 자동 처리
  • @nestjs/schedule 또는 BullMQ 기반으로 확장 가능

🗂️ 1️⃣ 폴더 구조 예시

batch/
 ├─ main.ts
 ├─ app.module.ts
 ├─ jobs/
 │   ├─ reservation-expire.job.ts       # 예약 만료 처리
 │   ├─ payment-sync.job.ts             # 결제 상태 동기화
 │   └─ notify.job.ts                   # 알림/이메일 발송
 ├─ services/
 │   └─ reservation.service.ts
 └─ utils/
     └─ logger.ts

⚙️ 2️⃣ package.json 스크립트

{
  "name": "nowswing-batch",
  "version": "1.0.0",
  "scripts": {
    "start": "node dist/main.js",
    "start:dev": "ts-node src/main.ts",
    "build": "tsc"
  },
  "dependencies": {
    "@nestjs/common": "^10.0.0",
    "@nestjs/core": "^10.0.0",
    "@nestjs/schedule": "^3.0.0",
    "@nestjs/config": "^3.0.0",
    "rxjs": "^7.8.1",
    "prisma": "^5.0.0",
    "@prisma/client": "^5.0.0"
  }
}

🚀 3️⃣ main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.createApplicationContext(AppModule);
  console.log('✅ Batch server started');
}

bootstrap();

💡 createApplicationContext() 는 HTTP 서버를 띄우지 않고
DI 컨테이너만 구동해서 배치 로직을 실행할 수 있게 합니다.


🧩 4️⃣ app.module.ts

import { Module } from '@nestjs/common';
import { ScheduleModule } from '@nestjs/schedule';
import { ConfigModule } from '@nestjs/config';
import { ReservationExpireJob } from './jobs/reservation-expire.job';
import { ReservationService } from './services/reservation.service';
import { PrismaClient } from '@prisma/client';

@Module({
  imports: [ConfigModule.forRoot(), ScheduleModule.forRoot()],
  providers: [ReservationExpireJob, ReservationService, PrismaClient],
})
export class AppModule {}

⏰ 5️⃣ 예약 만료 처리 Job — jobs/reservation-expire.job.ts

import { Injectable, Logger } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
import { ReservationService } from '../services/reservation.service';

@Injectable()
export class ReservationExpireJob {
  private readonly logger = new Logger(ReservationExpireJob.name);

  constructor(private readonly reservationService: ReservationService) {}

  // 매 1분마다 실행
  @Cron(CronExpression.EVERY_MINUTE)
  async handleExpiredReservations() {
    this.logger.log('🔍 Checking expired reservations...');
    await this.reservationService.cancelExpiredReservations();
    this.logger.log('✅ Reservation expire job done.');
  }
}

🗃️ 6️⃣ 예약 처리 서비스 — services/reservation.service.ts

import { Injectable } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class ReservationService {
  constructor(private prisma: PrismaClient) {}

  async cancelExpiredReservations() {
    const now = new Date();

    const expired = await this.prisma.reservation_v2.findMany({
      where: {
        status: 'HOLD',
        expiredAt: { lt: now },
        deletedAt: null,
      },
    });

    for (const r of expired) {
      await this.prisma.reservation_v2.update({
        where: { id: r.id },
        data: { status: 'CANCELLED', updatedAt: now },
      });
      console.log(`⛳ Reservation ${r.id} canceled`);
    }
  }
}

🧰 7️⃣ 실행 방법

# 1. 빌드
npm run build

# 2. 실행 (HTTP 서버 없음)
node dist/main.js

또는 개발 중에는:

npm run start:dev

⚙️ 8️⃣ 확장 방안 (대용량 환경)

기능 방법

동시성 처리 향상 Promise.all() / BullMQ Queue 적용
다중 코어 활용 PM2 Cluster (pm2 start dist/main.js -i max)
AWS 서버리스로 전환 Lambda + EventBridge 스케줄링
결제 동기화/정산 추가 별도 job 파일 추가 (payment-sync.job.ts)

✅ 결론

이 구조는 다음과 같은 특징을 가집니다 👇

  • NestJS 환경 그대로 → 기존 서비스 로직 재사용
  • @nestjs/schedule 기반 → 외부 툴 없이 크론 스케줄링 가능
  • Prisma/MySQL과 자연스럽게 연동
  • Node.js의 비동기 I/O로 수천 건 예약 만료도 효율적으로 처리

원하신다면 지금 구조에
👉 결제 상태 동기화(Stripe), 예약 리마인드 알림(SMS/Email) 까지 포함한
멀티 잡(batch) 통합 구조로 확장된 버전도 만들어드릴까요?

반응형