Notice
Recent Posts
Recent Comments
Link
«   2025/07   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

Mini

[Nest] Interceptor => logger 구현, @Transactional 구현 본문

JS/Nest.js

[Nest] Interceptor => logger 구현, @Transactional 구현

Mini_96 2024. 9. 22. 22:29

* 개념

  • res, req에 둘다 실행되는 특징이 있다.
  • 로거, @Transactinoal 등에 유용하다.
  • res, req에 모두 반복되는 공통 로직을 처리하고싶을때 사용하면 좋다.

 

* 로거구현

import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from "@nestjs/common";
import { Observable, map, tap } from "rxjs";
import {v4 as uuid} from 'uuid';

@Injectable()
export class LogInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler<any>): Observable<any> {
    /**
     * 요청이 들어올때 REQ 요청이 들어온 타임스탬프를 찍는다.
     * [REQ] {uuid} {요청 path} {요청 시간}
     *
     * 요청이 끝날때 (응답이 나갈때) 다시 타임스탬프를 찍는다.
     * [RES] {uuid} {요청 path} {응답 시간} {얼마나 걸렸는지 ms}
     */
    const now = new Date();
    const uid = uuid();

    const req = context.switchToHttp().getRequest();

    // /posts
    // /common/image
    const path = req.originalUrl;

    // [REQ] {요청 path} {요청 시간}
    console.log(`[REQ] [UUID: ${uid} ] ${path} ${now.toLocaleString('kr')}`)

    // return next.handle()을 실행하는 순간
    // 라우트의 로직이 전부 실행되고 응답이 반환된다.
    // observable로
    return next
      .handle()
      .pipe(
        tap(
          // [RES] {요청 path} {응답 시간} {얼마나 걸렸는지 ms}
          (observable)=>
            console.log(`[RES] [UUID: ${uid} ] ${path} ${new Date().toLocaleString('kr')} 
            ${new Date().getMilliseconds() - now.getMilliseconds()}ms`),
        ),
      );
  }
}
app.useGlobalInterceptors(new LogInterceptor());

결과

 

* @Transactional 구현

  • 문제
    • 트랜잭션 할때마다 반복되는 코드들..

 

  • 해결 : interceptor로 반복되는 로직을 처리해버리자
import {
  CallHandler,
  ExecutionContext,
  Injectable,
  InternalServerErrorException,
  NestInterceptor
} from "@nestjs/common";
import { catchError, Observable, tap } from "rxjs";
import { DataSource } from "typeorm";

/**
 * req에 qr을 넣어주기
 * res가 controller에 들어가기전에 startTransaction
 * 끝난후 commit, rollback
 */
@Injectable()
export class TransactionInterceptor implements NestInterceptor {
  constructor(
    private readonly dataSource : DataSource,
  ) {
  }

  async intercept(context: ExecutionContext, next: CallHandler<any>): Promise<Observable<any>> {

    const req = context.switchToHttp().getRequest();

    const qr = this.dataSource.createQueryRunner();

    await qr.connect();
    //이 시점에서 같은 qr을 사용해야 트랜잭션이 묶인다!! -> 즉, qr을 인자로 전달해야한다.
    await qr.startTransaction();


    /**
     * next.handle후에는 로직을 마친 res가 올때 실행되는 코드.
     */
    return next
      .handle()
      .pipe(
        catchError(async (e)=> {
            await qr.rollbackTransaction();
            await qr.release();

            throw new InternalServerErrorException(e.message);
          }
        ),
        tap( async() => {
            await qr.commitTransaction();
            await qr.release();
          }
        ),
      );

  }

}
  • 문제2 : qr을 어케전달?
  • 해결 : req에 넣으면됨 ㅋㅋ
req.qr = qr;
  • 유능한 개발자의 구현 : 데코레이터로 만들어서 transactional interceptor와 같이 사용되는지 확인후 qr을 반환하도록 만듬.
import { createParamDecorator, ExecutionContext, InternalServerErrorException } from "@nestjs/common";

/**
 * req에 넣어준 qr을 검사하고
 * qr을 리턴해주는 데코레이터
 *
 */
export const QueryRunner = createParamDecorator((data, context : ExecutionContext) => {
  const req = context.switchToHttp().getRequest();

  //FE와 BE의 평화가 찾아옴.
  if (!req.qr) {
    throw new InternalServerErrorException('' +
      'QueryRunner 데코레이저는 Transactional Interceptor 와 함께 사용해야합나다. ');
  }

  return req.qr;
});
  • 완성된 컨트롤러 코드
    • try-catch문도 필요없음.
    • 인터셉터에서 에러잡아주니까.
    • 비즈니스로직 에만 집중 가능.
@Post()
@UseGuards(AccessTokenGuard)
@UseInterceptors(TransactionInterceptor)
async postPost(
  @User('id') userId : number,
  @Body() body : CreatePostDto,
  @QueryRunner() qr: QR, //req에서 qr만 파싱해옴.
  ){

  //로직실행
  const post = await this.postsService.createPost(userId ,body, qr);

  /**
   * body의 image 이름들에대해
   * 각각 정보들을 넣어서 이미지를 만듬(db에 저장)
   * 주인은 post
   */
  for(let i=0;i<body.images.length;i++){
    await this.postsImagesService.createPostImage({
      post,
      order:i,
      path:body.images[i],
      type:ImageModelType.POST_IMAGE,
    }, qr);
  }

  return this.postsService.getPostById(post.id, qr);

}

 

'JS > Nest.js' 카테고리의 다른 글

[Nest] Exception Filter 구현  (0) 2024.09.22
[Nest] Transaction 구현  (0) 2024.09.22
[Next] 연관관계 매핑, ImageModel 생성 구현  (0) 2024.09.22
[Nest] 이미지 업로드 구현V2  (0) 2024.09.22
[Nest] 이미지 업로드 구현 V1  (0) 2024.09.22