사용한 라이브러리는 typeorm-transactional이다.
사용법은 내부적으로 cls-hooked 라이브러리를 사용하여 @Transactional() 메서드 데코레이터를 제공하고 있어서
트랜잭션 처리를 원하는 메서드에 데코레이터를 달고 사용하면 된다.
여기서 알아야 할 것이 CLS인데 이는 "Continuation Local Storage"의 약어로,
각각의 실행 컨텍스트에서 데이터를 공유할 수 있도록 해주는 라이브러리이다.
JavaScript가 단일 스레드이고 NodeJS가 이벤트 루프를 사용하여 비동기 코드를 처리하기 때문에 요청으로 들어온 각각의 실행 컨텍스트를 계속 추적하기 위해선 Request 객체 자체를 계속 넘겨주어야 하는데 이를 CLS를 통해 간편하게 해결한다.
그래서 @Transactional() 데코레이터가 달려있다면 독립된 실행 컨텍스트를 생성하고,
각각의 컨텍스트 내부에서는 local 변수처럼 데이터를 공유하고 접근할 수 있기 때문에
typeorm-transactional에서 설정한 key값으로 동일한 트랜잭션에 접근하여 쿼리들을 진행시킬 수 있는 것.
이제 어떻게 사용하는지 알아보자.
// main.ts
import { initializeTransactionalContext } from 'typeorm-transactional';
async function bootstrap() {
initializeTransactionalContext();
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(
new ValidationPipe({
transform: true,
}),
);
await app.listen(3000);
}
bootstrap();
위는 initializeTransactionalContext로 컨텍스트 간에 데이터를 격리하고, 공유할 수 있는 메커니즘을 제공하는 cls-hooked의 NameSpace를 초기화 하는 과정이다.
다음은 TypeORM의 Entity들을 트랜잭션에서 사용할 수 있도록 DataSource를 추가한다.
const typeOrmModuleOptions = {
useFactory: async (
configService: ConfigService,
): Promise<TypeOrmModuleOptions> => ({
namingStrategy: new SnakeNamingStrategy(),
type: 'mysql',
username: configService.get('DB_USERNAME'),
password: configService.get('DB_PASSWORD'),
host: configService.get('DB_HOST'),
port: configService.get('DB_PORT'),
database: configService.get('DB_NAME'),
entities: [...],
synchronize: configService.get('DB_SYNC'),
logging: true,
}),
inject: [ConfigService],
async dataSourceFactory(option: DataSourceOptions) { // 여기
if (!option)
throw new Error('Invalid options passed');
return addTransactionalDataSource(new DataSource(option));
}
};
이제 트랜잭션을 사용해야 하는 메소드에 @Transactional 데코레이터를 붙여서 트랜잭션을 사용하면 된다!