프론트엔드가 아닌 백엔드 분야기 때문에 전문성이 떨어질 수 있다는 점 유의해서 글 읽어주시면 감사하겠습니다..
백엔드를 공부해보기로 결심하고 처음에는 NestJS를 써보려고 했다.
하지만 주위 백엔드 개발자들이 NestJS를 공부하면 백엔드를 공부하는 게 아닌 NestJS를 공부하는 거라 추천하지 않는다고 해서 typestack에서 제공하는 typedi, routing-controllers 그리고 typeorm 기반의 가벼운 라이브러리를 사용해 node.js 프로젝트를 만들고 공부해보려고 한다.
먼저 typedi에 대해 공부해보려고 한다.
의존성 주입?
typedi는 이름에서도 알 수 있듯이 JavaScript, TypeScript에서 사용할 수 있는 의존성 주입 툴이라고 한다.
의존성 주입이라는 단어가 생소해 이 개념에 대해 먼저 공부해보려고 한다.

의존성은 객체 지향 개발을 공부해 보았다면 많이 들어보았을 단어이다.
말 그대로 요소들이 서로 의존하는 걸 의미하는 것이다.
예를 들어 Student라는 클래스에 Pencil이라는 클래스가 있다면 Student는 Pencil에 대한 의존성을 갖는 것이다.
개발을 하다 보면 의존성은 필수적이다.
하지만 잘못된 설계로 의존성을 가지게 된다면 서로의 변경에 민감해지고 최악의 경우 변경마다 추가적인 작업을 해야 할 수도 있다.
잘못된 의존성을 막기 위해 존재하는 방법이 바로 의존성 주입이다.
의존성 주입은 외부에서 의존성을 주입하는 방식으로 직접 코드로 보여주자면
class UserRepository {
getUsers() {
}
}
class UserService {
private userRepository: UserRepository;
constructor() {
this.userRepository = new UserRepository();
}
getUsers() {
return this.userRepository.getUsers();
}
}
위 코드는 의존성 주입을 하지 않은 예시이다.
UserService는 UserRepository에 대해 직접 의존하고 있다.
만약에 UserRepository가 변경되거나 UserService에서 다른 Class를 사용해야 하는 경우 UserService를 수정해야 한다.
SOLID 원칙을 공부해 봤다면 의존성 역전 원칙에 대해 들어보았을 것이다.
의존성 역전 원칙이란 자신보다 변하기 쉬운 것에 의존해서는 안된다는 원칙이다.
위와 같은 경우에서 UserRepository가 변하기 쉬운 Class라면 이 원칙에 어긋나는 행동이다.
interface IUserRepository {
getUsers(): User[];
}
class UserRepository implements IUserRepository {
getUsers() {
}
}
class UserService {
private userRepository: IUserRepository;
constructor(userRepository: IUserRepository) {
this.userRepository = userRepository;
}
getUsers() {
return this.userRepository.getUsers();
}
}
이번에는 의존성 주입을 적용한 구조를 살펴보자
변화가 적은 인터페이스에 의존하게 되었고,
UserRepository가 아닌 IUserRepository를 상속받은 다른 Class를 주입하더라도 문제가 없다.
의존성과 의존성 주입을 했을 때 얻는 이점에 대해 우선 알아보았다.
그렇다면 typedi는 이 의존성 주입을 어떻게 도와준다는 걸까?
typedi
세팅
typedi를 사용하기 위해서는 아래 커맨드를 통해 두 개의 패키지를 설치해야 한다.
npm install typedi reflect-metadata
그리고 tsconfig.json을 아래와 같이 수정해야 한다.
// file: tsconfig.json
{
//...
"compilerOptions": {
//...
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
//...
}
//...
}
기능
세팅은 전부 되었다
우선 기본적인 기능을 설명해 보자면 typedi는 데코레이터 기반으로 작동하게 된다.
크게 두 가지 기능이 있다.
컨테이너에 담기,
컨테이너에 있는 객체를 주입받기
먼저 컨테이너에 담는 방법은 두 가지가 있다.
@Service()
class InjectedClass {}
class InjectedClass {}
Container.set('InjectedClass', InjectedClass)
위 두 방법 모두 동일하게 작동하고 둘 다 Container에 잘 담기게 된다
주입받는 방법은 3가지가 있다.
@Service()
class InjectedClass {}
@Service()
class ExampleClass {
constructor(public injectedClass: InjectedClass) {}
}
@Service()
class InjectedClass {}
@Service()
class ExampleClass {
@Inject()
injectedClass: InjectedClass;
}
@Service()
class InjectedClass {}
@Service()
class ExampleClass {
injectedClass: InjectedClass = Container.get(InjectedClass);
}
constructor를 사용하는 방법, @Inject()를 사용하는 방법, Container.get()을 사용하는 방법이 있다.
마지막으로 @InjectMany()라는 데코레이터가 있는데 이 데코레이터는 좀 특이하다.
아마 코드로 설명하는 게 더 좋을 것 같다.
interface Car {
name: string;
}
@Service({ id: 'cars', multiple: true })
class Bmw implements Car {
name = 'BMW';
}
@Service({ id: 'cars', multiple: true })
class Mercedes implements Car {
name = 'Mercedes';
}
@Service({ id: 'cars', multiple: true })
class Toyota implements Car {
name = 'Toyota';
}
@Service()
class TestServiceWithParameters {
constructor(@InjectMany('cars') public cars: Car[]) {}
}
위와 같이 동일한 id를 가진 Service들을 전부 주입받는 데코레이터로 꽤 유용할 것 같다.
(조금 더 디테일한 옵션 값들은 글이 너무 어려워질 것 같아서 설명에서 제외했다.)
오늘은 의존성 관리를 도와주는 typestack의 모듈 중 typedi에 대해 공부해 보았다.
다음 글에는 라우팅을 도와주는 routing-controllers에 대해 알아볼 계획이다.
잘못되거나 더 궁금한 점 있으시면 댓글로 달아주시면 감사하겠습니다!
'back-end' 카테고리의 다른 글
| 자세한 예시로 알아보는 REST API의 설계 방법 (1) | 2023.08.21 |
|---|