NEST.JS의 문제..?
- Nest.js의 문제점으로 느낀 것이 바로 불필요한 트랜잭션 코드입니다.
- 스프링 AOP 기술을 통해 트랜잭션 어노테이션을 사용해오던 저에게는 매우 불필요한 코드였습니다.
- 따라서 이를 분리하기 위해 인터셉터와 데코레이터를 사용하였습니다.
트랜잭션 인터셉터
- Nest.js의 인터셉터는 크게 3가지 기능을 할 수 있습니다
- 요청 전 로직, 요청 성공 시 로직, 예외 터질 시 로직
- 따라서 요청이 컨트롤러로 들어가기전에 쿼리 러너를 만든 후 트랜잭션을 시작합니다.
- 이떄 쿼리 러너를 http request 객체에 넣어줍니다. 이는 나중에 재사용하기 위함입니다.
- 예외가 터지면 catchError 기능을 통해 트랜잭션을 롤백합니다.
- 요청이 성공적으로 마무리되면 트랜잭션을 커밋합니다.
@Injectable()
export class TransactionInterceptor implements NestInterceptor {
constructor(private readonly dataSource: DataSource) {}
async intercept(
context: ExecutionContext,
next: CallHandler,
): Promise<Observable<any>> {
const request = context.switchToHttp().getRequest();
const qr = this.dataSource.createQueryRunner();
await qr.connect();
await qr.startTransaction();
request.queryRunner = qr;
return next.handle().pipe(
catchError(async (e) => {
await qr.rollbackTransaction();
await qr.release();
throw new TransactionRollback();
}),
tap(async () => {
await qr.commitTransaction();
await qr.release();
}),
);
}
}
쿼러 러너 데코레이터
- 쿼리 러너 데코레이터를 트랜잭션을 적용할 각 컨트롤러에 붙여 줍니다.
- 이를 통해 HTTP 요청 안에 쿼리 러너가 있는지 파악하고 만약 쿼리 러너가 없다면 예외를 터뜨립니다.
export const QueryRunner = createParamDecorator(
(data, context: ExecutionContext) => {
const request = context.switchToHttp().getRequest();
if (!request.queryRunner) {
new TransactionInterceptorRequired();
}
return request.queryRunner;
},
);
마무리
- Nest.js에 있을게 다있는데 아쉽게도 트랜잭션에 대해 매번 불필요한 코드가 너무 많았습니다.
- 이를 계선해볼 작업을 고민했고 쿼리 러너 데코레이터와 트랜잭션 인터셉터를 구현해보는 방법을 고민했습니다.