🐳 문제
typeORM이 0.3 버전을 출시하면서 기존에 레포지터리에 의존성을 주입 해주던 @InjectRepository() 데코레이터가 사라지게 됐다.
그래서 TypeORM 0.3 버전에서는 어떻게 레포지터리 패턴을 사용하는지 TypeORM 공식 문서를 통해 확인해 보았다.
아래는 TypeORM 공식문서에서 발췌한 Repository 패턴을 구현하는 방법이다.
import { User } from "./entity/User"
const userRepository = dataSource.getRepository(User)
const user = await userRepository.findOneBy({
id: 1,
})
user.name = "Umed"
await userRepository.save(user)
위의 코드에서는 의존성 주입을 활용하는 것이 아니라 필요한 곳에서 dataSource를 통해 Repository를 만들어 사용했다.
그래서 처음에는 이렇게 필요한 곳에서 레포지터리를 만들어 사용하려고 했다.
그러나 이는 예상치 못한 문제를 발생시켰다...
🐳 의존성 주입의 문제
typescript와 express 기반에서 나는 Controller - Service - Repository - Database 로 이어지는 레이어를 구현하려고 했다.
그리고 의존성 주입을 위해 typedi 모듈을 사용했다.
하지만 이 때 Repository가 Class 가 아닌 객체로 존재하기 때문에 typedi의 @Service() 데코레이터를 사용해 Container에 등록할 수 없었고 이는 자연스럽게 Service 레이어에 Repository를 주입할 수 없는 문제를 야기했다.
그래서 Service 레이어의 constructor 에서 바로 Repository를 생성하는 방법을 생각해 보았다.
@Service()
export class UserService {
constructor(
private readonly userRepository = dataSource.getRepository(User)
) {}
}
위와 같은 코드로 말이다.
하지만 이 또한 다른 문제를 야기했다.
🐳 TDD 에서의 문제
위의 방법대로 레포지터리를 구현한 후 Service Layer를 test 하려고 해보자
그러면 dataSource을 mocking한 후 mocking한 userRepositoy를 만들고 그 레포지터리의 메소드인 findOne 을 mocking한 후 값을 지정해 원하는 값을 resolve하게 해주어야 한다.
말만 들어도 구현이 복잡하다 .. ( 실제로 구현 못했다 .. )
이 상황에 놓이니 유튜브 개발바닥의 향로님이 하신 말이 생각났다.
개발바닥 채널에서 향로님이 자바지기(박재성)님에게 TDD를 위해서 코드를 변경할 필요가 있냐고 물어보니 박재성님이 Test Code를 구현하기 어려운 코드는 뭔가 잘못된 코드이기 때문에 수정할 필요가 있다고 대답했다고 하셨다.
이 말을 생각하면서 내 코드의 문제점이 있다고 생각하고 Repository Class를 구현해야 겠다고 생각했다.
🐳 TypeORM v0.3 에서 Repository Layer의 구현
그렇게 며칠을 이 문제를 해결하기 위해 github와 구글을 찾아보던 도중 Nest.js에서 비슷한 문제를 겪고 있는 사람들의 글을 찾게 되어 해결 방법을 찾게 되었다.
import { Service } from "typedi";
import { AppDataSource } from "../data-source";
import { User } from "./User";
import { Repository } from "typeorm";
@Service()
export class UserRepository extends Repository<User> {
constructor() {
super(User,AppDataSource.createEntityManager());
}
}
typeORM에서 제공하는 Respository Class를 상속 받아 새로운 class를 만드는 방법이었다.
이 때 super 부분을 살펴보면
target : Repository를 구현할 Entity
manager : Entitymanger
이렇게 두 가지를 입력하라는 것을 알 수 있다.
그래서 User Entity와 dataSource의 createEntityManager()를 통해 manager를 만들어 주입해 Custom Repository Class를 구현했다.
( AppDataSource는 typeORM의 database 연결을 위한 dataSource의 실제 객체를 구현한 것이다. )
🐳 Repository Class와 ts-mockito를 활용한 Test Code
describe("User Service Test", () => {
it('should call repository.findOneBy', async () => {
const user = new User();
user.name = "Hoo"
const mockedRepository = mock(UserRepository)
when(mockedRepository.findOneBy(deepEqual({name:user.name}))).thenResolve(user)
const respository = instance(mockedRepository)
const userService = new UserService(respository);
})
})
위의 코드를 살펴보면 userService에 mocking한 respository를 주입하는 것이 정말 간편해 진 것을 확인할 수 있다.
( Jest가 아닌 ts-mockito를 활요한 test code 이다. )
개발바닥 향로님의 블로그를 보면 더 쉽게 이해되실 겁니다.
이는 ts-mockito를 썼기 때문도 있지만 Repository class를 구현해 userService에 주입하는 것이 간편해진 영향도 존재한다.
🐳 회고
테스트코드와 실제 코드는 밀접한 관련이 있다는 것을 한 번 더 알게 되었다.
테스트코드를 구현하는 것이 어렵다면 내 코드에 문제가 있다는 것이다 !
정말로 공부할 것인 산더미다 .. 그래도 한 고비 넘었다 !
'Node.js' 카테고리의 다른 글
[PrismaORM] upsert 사용 시 Unique constraint failed on the fields 문제 해결 (0) | 2024.08.23 |
---|---|
[ExpressJS] Javascript로 Enum 사용하기 - 오픈카톡방 공유 후기 (0) | 2024.01.20 |
Typescript로 Jest 사용하기 (0) | 2023.07.06 |
Layered Architecture 구현하기 (0) | 2023.07.05 |
TypeORM + TypeScript + Express 개발환경 세팅 (0) | 2023.07.03 |