Typeorm은 orm이긴 하지만 typeorm이 작성해준 query가 바보같이 짜이기도 하고 최적화를 하기 위해서는 query를 어느정도 짜주어야한다.
또한 서비스 규모가 어느정도 있거나 앞으로의 확장성을 위한다면 data mapper와 Custom repository를 사용하게 된다.
Custom repository를 통한 transaction을 진행하는 경우 서비스에서 @Transaction
이란 데코레이터를 보통 사용하였지만 0.3^에서 deprecated 되었다.
또한 TransactionManager
역시 deprecated 되어 service layer에서 transaction을 사용하고자 하면 transactionManager
를 사용하는 것이 사실상 강제인데 이 때 custom repository를 사용하려면 withRepository
를 사용하여 호출하여야 한다.
withRepository를 사용하여 호출하지 않으면 트랜잭션이 보장되지 않는데 링크를 보면 확일할 수 있다.
await connection.transaction(async manager => {
// in transactions you MUST use manager instance provided by a transaction,
// you cannot use global managers, repositories or custom repositories
// because this manager is exclusive and transactional
// and if let's say we would do custom repository as a service
// it has a "manager" property which should be unique instance of EntityManager
// but there is no global EntityManager instance and cannot be
// thats why custom managers are specific to each EntityManager and cannot be services.
// this also opens opportunity to use custom repositories in transactions without any issues:
const userRepository = manager.getCustomRepository(UserRepository); // DONT USE GLOBAL getCustomRepository here!
await userRepository.createAndSave("Timber", "Saw");
const timber = await userRepository.findByName("Timber", "Saw");
});
@Transaction
이 사라져서 조금 짜증나지만 이를 감수하고 withRepository를 사용해보았다.
아래는 간단한 예제 코드다. 테스트에 사용된 typeorm 버전은 0.3.5이다.
...
// user.service.ts
try {
const result = await connection.transaction(async (transactionalEntityManager) => {
transactionalEntityManager.withRepository(this.userRepository).create(user);
return result;
});
return result;
} catch (e) {
this.logger.error(e);
throw new Error('Create User Failed',e);
}
...
// exampleController.ts
try {
const userServiceInstance = Container.get(UserService);
// @ts-ignore
const test = await userServiceInstance.create({
user_type: 3000,
phone_number: '01012345678',
gender: 'M',
age_group: 30,
});
return res.status(200).json({ test });
} catch (e) {
logger.error(e);
return next(e);
}
};
이렇게 진행하고 controller에서 다음과 같이 작성하고 호출을 하게 되면
위 이미지 와 같은 오류가 발생한다.
Cannot set property metadata of #<Repository> which has only a getter
해당 오류는 DI진행시 발생하는 오류이다.
Container.get("something)" // 해당 line에서 오류 발생
위 오류에 관한 Issue는 링크에서 확인 할 수 있다.
해당 이슈의 로직을 자세히 살펴보면 transactoin 관련 로직이 아닌 dependency inject 과정에서 nestjs에서 발생한 것을 알 수 있다.
즉, 근본적인 이유는 transaction뿐 아니라 custom repository dependency injection에 문제가 있는 것이다.
extends가 조금 의심스러워 repository에서 extends를 제거하게 되면 정상적으로 작동한다.
@EntityRepository(User)
export class UserRepository // extends Repository<User> 주석처리
하지만 이렇게되면 transaction을 사용하지 못하게 된다.