본문 바로가기
TypeScript

[Nest.js] Custom Interceptor

by yonikim 2022. 4. 11.
728x90

상품은 이커머스의 꽃으로, 검색 및 다른 서비스들에서 상품 데이터를 Meta 로 들고 있다 보니 상품 데이터가 변경될 때마다 Sync 를 맞춰줘야 했다.

N분에 한번씩 스케쥴링 하여 ETL 하는 방법도 있겠지만 그렇게 하면 데이터베이스에 과부하가 심할거 같아서 상품의 create/update/delete 이벤트가 발생할 때마다 AWS SNS 에 메시지를 보내는 방식으로 구현하기로 결정했다.

상품 backend api 내에 있는 function 별로 코드 내부에 삽입하는 방법도 있지만, 코드가 너무 더러워진다고 생각되어 Custom Interceptor 를 만들기로 결정했다. 

 

▷ sns.interceptor.ts

우리는 grpc 통신을 이용하여 각 서비스 별로 통신하고 있었기에 Service 단에 해당 Interceptor 를 붙여야 했다. Service 단에 붙일 경우 body 데이터만 가져올 수 있어서 함수명을 파싱하여 method 를 구분해야 했다. 

* Controller 단에 붙일 경우 `context.switchToHttp().getRequest()` 를 사용하면 request 정보를 모두 가져올 수 있다. 

@Injectable()
export class SnsInterceptor implements NestInterceptor {
  constructor(@Inject(SnsService) private readonly snsService: SnsService) {}

  private async parseEventName(methodName: string) {
    let event = "select";

    if (methodName.toLowerCase().includes("create")) {
      event = "create";
    }
    if (methodName.toLowerCase().includes("update")) {
      event = "update";
    }
    if (methodName.toLowerCase().includes("delete")) {
      event = "delete";
    }

    return event;
  }

  async intercept(
    context: ExecutionContext,
    next: CallHandler
  ): Promise<Observable<any>> {
    if (isLocal()) return next.handle();
    const grpcData = context.switchToRpc().getData();

    const grpcServiceName = context.getClass().name;
    const grpcMethodName = context.getHandler().name;

    const event = await this.parseEventName(grpcMethodName);

    if (!["create", "update", "delete"].includes(event)) {
      return next.handle();
    }

    const idData = [];
    if (event === "delete") {
      await this.sendMessage(event, grpcServiceName, grpcData);
      return next.handle();
    }

    return next.handle().pipe(
      tap(async (response) => {
        await this.sendMessage(event, grpcServiceName, grpcData);
        return next.handle();
      })
    );
  }

  private async sendMessage(event: string, service: String, data: any) {
    const dto: CreatePublishDto = {
      protocol: SNS_PROTOCOLS.Sqs,
      message: JSON.stringify({ event, service, data }),
    };
    await this.snsService.publish(dto);
  }
}

 

 

Custom하여 만든 interceptor 를 사용하는 방법은 두가지가 있기 때문에, 편한 방법으로 사용하면 될거 같다. 우리는 모든 테이블의 이벤트 정보가 필요하지 않아서, 필요한 테이블의 Service 에만 Inject 해주는 2번 방법을 사용했다. 

 

1. 전역적으로 사용할 경우 useGlobalInerceptors 사용하기

 main.ts

async function bootstrap() {
  const app = await NestFactory.create(GatewayProductModule, {
    logger: ["log", "error", "warn", "debug"],
  });
  app.enableCors({
    origin: true,
    credentials: true,
  });
  app.useGlobalInterceptors(new SnsInterceptor());
  app.useGlobalPipes(new ValidationPipe({ transform: true }));

  await app.listen(3000);

}

bootstrap();

 

2. 특정 Controller 혹은 Service 코드 내부에 UseInterceptors 사용하기

@Controller()
@UseInterceptors(SnsInterceptor)
export class ProductOptionService {
...
}

 

 

728x90