typeorm 0.3.7 버전을 기준으로 작성중이다
고스락 티켓 예매 프로젝트(이하 고티켓 ) 에선 repostiory를 아예 파일로 따로 만들어서
Repository<Entity> 를 주입받아서 새로운 Repository로 만들어서 사용중이다.
이에 맞춰서 공식문서랑은 조금 다른 방식으로 트랜잭션을 풀어낸 방법을 공유하고자한다.
이글을 통해 얻어갈 수 있는것들
- 트랜잭션 시작한 쿼리러너로 Repository를 받아오는 방법
- 콜백이나 , 엔티티매니저가 아닌 Repository 로 트랜잭션시 사용하기
이 블로그의 내용은 고스락 티켓 예매 프로젝트에서 사용중인 소스이다
공식 문서에 나오는 방식에는 두가지방식을 예시로 들었는데
데이타 소스에서 트랜젝션을 콜백으로 넘겨줘서 엔티티 매니저로 엔티티를 관리하는방법
쿼리 러너로 트랜잭션을 시작해서 엔티티 매니저로 엔티티를 관리하는 방법
크게 두가지다. 고티켓은 UserRepository 처럼 레포지토리를 따로 만들어서하고있는데
보통의 서비스 레이어에서 걸려야할 트랜잭션에
위 두가지 방식으로 구성하면 애써 커스텀해서 만들어놓은 레포지토리를 쓰지도못하니.. 참 고민이였다. ( 엔티티 매니저를 통해서 쿼리를 날리기 때문 )
하나의 단적인 해결방법으로는 위두가지 방법을 이용해서 쿼리러너나 엔티티매니저를 인자로 넘겨줘서
그걸이용해서 엔티티를 변경하는 로직을 작성해야하는데...
이러면 위 사진처럼 Repository<User> 주입받은걸 쓰지도 못하고.
트랜잭션용도인거 아닌거를 구별해서 메소드를 써야하니 고민이 되는상황.
서비스 레이어에서 레포지토리를 DEFAULT Scope 으로 주입받은상황이고....
갑자기 userRepository의 엔티티 매니저를 트랜잭션마다 바꿔줄수 없는 상황이고...
그렇다고 엔티티 메니저를 레포지토리로 인자로 넘겨주긴... 너무나도 소스짜기가 귀찮을것 같았다.
그래서 트레이드 오프를 해서 쿼리러너로 트랜잭션을 시작한뒤
주입받은 Repository 타입으로 새로운 레포지토리를 만들어 동일한 커넥션을 유지하는 레포지토리를 쓰기로 했다.
+ 별첨.. 스프링은 스레드 로컬있어서 트랜잭션 전파도 되지마는...
네스트는 비슷하게 cls-hook 이용한 방법이 있지만
이거뭐 쓰기도 참 애매하다 마지막 커밋이 일년을 넘었다
트랜잭션 시작한 queryRunner로 Repository를 받아오는 방법
async registerUser(): Promise<ResponseRegisterUserDto> {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
const userRepositoryFromDataSource = queryRunner.manager.getRepository(User);
const connectedUserRepository = new UserRepository(
userRepositoryFromDataSource
);
try {
const user = new User();
const signUser = await connectedUserRepository.saveUser(user);
await queryRunner.commitTransaction();
} catch (e) {
await queryRunner.rollbackTransaction();
throw e;
} finally {
await queryRunner.release();
}
}
src/auth/auth.service.ts
매우 간략화한 소스이다... typeorm 모듈을 임포트 시키면 글로벌하게 DataSource를 주입받을 수 있다.
해당 데이타소스로부터 쿼리러너를 받고, 일단먼저 connect 와 트랜잭션을 시작한뒤에
User 엔티티로 Repository<User> 을 땡겨온다.
아래사진처럼 우리가 만든 UserRepository 생성자로 넘겨주면서 커넥션이 연결 된 상태인 connectedUserRepository를 받아온다.
그러면 트랜잭션이 진행중인 우리가 만든 리포지토리를 쓸수가 있다. 아래 그림처럼 try catch 사이에 일부로 에러를 내면 롤백을 당한다.
좀더 편하게 트랜잭션 시작된 레포지토리 받아오기
export function getConnectedRepository<T>(
type: { new (repository: Repository<ObjectLiteral>): T },
queryRunner: QueryRunner,
entity: EntityTarget<ObjectLiteral>
): T {
const userRepositoryFromDataSource =
queryRunner.manager.getRepository(entity);
return new type(userRepositoryFromDataSource);
}
// usage
const connectedOrder = getConnectedRepository(
OrderRepository,
queryRunner,
Order
);
const connectedTicket = getConnectedRepository(
TicketRepository,
queryRunner,
Ticket
);
src/common/funcs/getConnectedRepository.ts
팀원들이 쓸 로직이라 좀 쓰기 쉽게 type 받아서 리턴해주는 메소드를 만들었다.
뭐 간단하다... 제네릭이 딱히 필요없어 보긴한데 결국 type을 생성해서 반환해주는 형식이다.
아래처럼 롤백이 정상적으로 이루어지는 것을 볼 수 있다.
트랜잭션이 필요한 부분에 repository를 주입받은거 말고 새로 생성해야한다는 단점이
있긴하지마는... 이게 최선인것같다.
고민해봐도 기존 구조를 크게 해치지않고 , 팀원들이 잘 따라와줄수 있는 소스의 변경 범위 정도가
적당해서 적용해 보았다.
인증 관련 서비스에 예시코드 미리 쫘놓으니 주문, 티켓 서비스 등에서도
팀원 분들이 잘 적용해 주셨다.
위방법을 이용해서 레포지토리 실 디비 테스트 할때도
스프링의 테스트 @Transactional 처럼 롤백 테스트 할 수있긴하다. 포스팅으로 함 적어보겠다!
'nest.js' 카테고리의 다른 글
[고스락 티켓 2.0] nestjs swagger 같은 코드 여러 응답 예시 만들기 (1) - @ApiProperty로 객체 만들기 (0) | 2022.08.14 |
---|---|
[고스락 티켓 2.0] nestjs db rollback repository 테스트 (0) | 2022.08.14 |
[고스락 티켓 2.0] nestjs redis forRootAsync 모듈 만들기 (0) | 2022.08.13 |
[고스락 티켓 2.0] nest js ValidationError custom (1) | 2022.08.13 |
[고스락 티켓 2.0] nest js 유저 role 기반 api 인가 (0) | 2022.08.13 |