Typeorm Cannot set property metadata of #<Repository> which has only a getter 오류

비모·2022년 4월 6일
1

Custom repository with typeorm and data mapper pattern

Typeorm은 orm이긴 하지만 typeorm이 작성해준 query가 바보같이 짜이기도 하고 최적화를 하기 위해서는 query를 어느정도 짜주어야한다.


또한 서비스 규모가 어느정도 있거나 앞으로의 확장성을 위한다면 data mapper와 Custom repository를 사용하게 된다.


Using transaction in 0.3^

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");
});

withRepository

@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 keyowrd 없이 사용

extends가 조금 의심스러워 repository에서 extends를 제거하게 되면 정상적으로 작동한다.

@EntityRepository(User)
export class UserRepository // extends Repository<User> 주석처리

하지만 이렇게되면 transaction을 사용하지 못하게 된다.

정리

  1. 규모있는 프로젝트에서 Dependency injection을 사용하며 Custom repository 사용하는 경우 Typeorm 0.3으로 업데이트 주의
  2. 정말정말 0.3을 사용하고 싶다면 custom repository에서 extends를 사용하지 x, 조잡한 프로젝트 구조 각오

0개의 댓글