프레임워크/NestJS

[NestJS] Dependency Injection과 NestJS

트리맨스 2022. 11. 5. 01:59
반응형

 

 

최근들어 비교적 삶이 한가해져서, 미루기만 했던 지식들을 정리하는 시간을 가지기로 했다. 이번 포스트에서는 DI (Dependency Injection)과 NestJS에서 해당 기능을 사용하는 방법에 대해서 정리해 볼려고 한다.

 

Dependency Injection 이란?


정의부터 알기 전에, 각 단어에 대해서 알아보자. Dependency는 의존성, Injection은 주입 이라는 단어이다. 각 단어에 대해서도 간단히 정리하자.

 

의존성

의존 관계를 가지는 것이다. 아래 TS 코드로 확인해 보자.

class Human {
  meal = new Meal();
}

class Meal {}

위 코드에서 Human 클래스는 Head 클래스를 내부에서 사용하게 되므로, Human 클래스는 Meal 클래스에 의존 관계가 생긴다.

 

주입

외부에서 객체를 생성하여 생성자를 통해 넣어주는 것이다.

class Human {
  meal: string;
  constructor(meal: string) {
    this.meal = meal;
  }
}

const human = new Human('Rice');

 

여기서 의존성과 주입을 합치게 되면, 다음과 같이 작성할 수 있다.

class Human {
  meal: Meal;
  constructor(meal: Meal) {
    this.meal = meal;
  }
}

class Meal {}

const human = new Human(new Meal());

 

만약 여기서 meal의 종류가 다양해지면 어떻게 될까? 예를 들어 Human이 밥도 먹고싶고, 샌드위치도 먹고싶고, 고기도 먹고 싶을 때 일일이 생성자를 만들어서 주입시켜야 할까? 아니다. 의존 관계 원칙을 이용해서 interface를 통해 변화하지 않는 무언가를 분리하는 것이 제일 좋다. 이럴 때 interface를 만들어서 분리하면 된다.

interface Meal {
  eat(): any;
}

class Rice implements Meal {
  eat() {
    console.log("eat Rice");
  }
}

class Sandwich implements Meal {
  eat() {
    console.log("eat Sandwich");
  }
}

class Meat implements Meal {
  eat() {
    console.log("eat Meat");
  }
}

class Human {
  constructor(private meal: Meal) {}

  public eatMeal() {
    this.meal.eat();
  }
}

const human = new Human(new Sandwich());
human.eatMeal();

아래 코드를 보면 식사와 관련된 모든 클래스는 Meal이라는 인터페이스에 의존하게 된다. 여기서 식사를 더 추가하고 싶으면 Meal 인터페이스를 상속받아 어떠한 메서드가 오는지 짐작이 가능하게 한다. 만약 식사와 관련된 예외적인 메서드가 있을 경우, instanceOf 가드를 사용하여 다음과 같이 작성할 수도 있다. 

 

interface Meal {
  eat(): any;
}

class Rice implements Meal {
  eat() {
    console.log("eat Rice");
  }
}

class Sandwich implements Meal {
  eat() {
    console.log("eat Sandwich");
  }

  cut() {
    console.log("cut Sandwich");
  }
}


class Human {
  constructor(private meal: Meal) {}

  public eatMeal() {
    this.meal.eat();
  }

  public cutMeal() {
    if (this.meal instanceof Sandwich) {
      this.meal.cut();
    } else {
      console.log("cannot cut");
    }
  }
}

const human = new Human(new Sandwich());
human.cutMeal();

위의 코드는 instanceOf를 사용하여 Meal 인터페이스에 등록된 메서드가 아닐 경우의 예외처리를 하게 된다. 

 

하지만 위와 같은 방식은 생성자마다 인스턴스를 만들어서 넣어줘야 한다. 클래스의 의존성은 끝이 없을 수 있다는 점에서, 해당 방식은 프로젝트가 커지면 커질수록 보기 불편할 것이다. 이러한 점을 typedi로 해결할 수 있다. 아래 코드에 예시가 있다.

import { Service, Container } from "typedi";
import "reflect-metadata";

interface Meal {
  eat(): any;
}

@Service()
class Rice {
  eat() {
    console.log("eat Rice");
  }
}

@Service()
class Human {
  constructor(private meal: Rice) {}

  public eatMeal() {
    this.meal.eat();
  }
}

const human = Container.get(Human);
human.eatMeal();

Service 데코레이터를 사용하고, 실제 클래스를 사용할 때 Container.get([클래스명]) 만 입력해주면, 의존성을 직접 주입할 필요 없이 생성자에 있는 객체가 알아서 주입이 된다. 해당 코드부터는 제어권이 개발자가 아니라, typedi가 가지게 되는 것이다. 이것을 제어권의 역전이라고 한다.

 

NestJS 에서의 DI


그렇다면, 위에서 설명한 이론들은 NestJS에서 어떻게 구현이 잘 되어 있을까? 공식 문서를 참고하면 쉽게 알 수 있다.

 

// cats.service.ts
import { Injectable } from '@nestjs/common';
import { Cat } from './interfaces/cat.interface';

@Injectable()
export class CatsService {
  private readonly cats: Cat[] = [];

  findAll(): Cat[] {
    return this.cats;
  }
}

공식 문서에 있는 자료이다. CatService는 Cat과 관련된 비즈니스 로직 (실제 계산하는 곳) 이 있는 클래스이다.

 

// cats.controller.ts
import { Controller, Get } from '@nestjs/common';
import { CatsService } from './cats.service';
import { Cat } from './interfaces/cat.interface';

@Controller('cats')
export class CatsController {
  constructor(private catsService: CatsService) {}

  @Get()
  async findAll(): Promise<Cat[]> {
    return this.catsService.findAll();
  }
}

CatsController는 컨트롤러이다. 외부 API와 비즈니스 로직을 연결시켜준다. 이 때 잘 보면 생성자에 CatsService가 들어가 있는 것을 볼 수 있다. 바닐라 ts는 생성자 주입, typedi는 데코레이터, NestJS에서는 다음과 같이 주입한다.

 

// app.module.ts
import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';
import { CatsService } from './cats/cats.service';

@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class AppModule {}

 

위에서 나온 기능들을 하나의 모듈로 묶어서 사용한다. 컨트롤러는 컨트롤러에 모두 담기고, 컨트롤러의 생성자인 CatsService는 provider에 담기게 된다. (CatsController에 주입을 하니까, 공급한다는 개념으로 보는 거 같다.) 이렇게 해서 DI를 구현한다.

 

하지만 Nest에서는 위와 같이 사용하는 것이 축약형이고, 기본형 provider와 커스텀 provider를 따로 설명해 주고 있다.

// standard (표준)
providers: [
  {
    provide: CatsService,
    useClass: CatsService,
  },
];
// useValue (바꿔치기)
 providers: [
    {
      provide: CatsService,
      useValue: mockCatsService,
    },
 ],
 
// token (문자열 토큰으로 주입, 해당 패턴 사용 시 아래와 같이 사용해야 함)
 providers: [
    {
      provide: 'CONNECTION',
      useValue: connection,
    },
 ],
 
 // use token
@Injectable()
export class CatsRepository {
  constructor(@Inject('CONNECTION') connection: Connection) {}
}

그냥 이런저런 식으로 주입하는 방법들이 있으니까, 알아서 잘 써먹으라는 것 같다. 마지막으로 사용한 공급자를 다른 곳에서도 써먹으려면, exports 안에 공급자를 추가하면 된다. 토큰 방식의 공급자는 토큰을 쓰면 된다.

 

 

참고자료

https://medium.com/@jang.wangsu/di-dependency-injection-%EC%9D%B4%EB%9E%80-1b12fdefec4f

 

[DI] Dependency Injection 이란?

디펜던시 인젝션, 의존성 주입에 대해 간단하게 작성해 봅니다.

medium.com

https://medium.com/@HoseungJang/typescript%EC%99%80-typedi%EB%A1%9C-%EC%9D%98%EC%A1%B4%EC%84%B1-%EC%A3%BC%EC%9E%85-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-5d83ef1977f9

 

TypeScript와 typedi로 의존성 주입 이해하기

아름다운 코드를 짜기 위해서 의존성 주입을 알아봅시다.

medium.com

https://docs.nestjs.com/providers

 

Documentation | NestJS - A progressive Node.js framework

Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Progamming), FP (Functional Programming), and FRP (Functional Reac

docs.nestjs.com

https://docs.nestjs.com/fundamentals/custom-providers

 

Documentation | NestJS - A progressive Node.js framework

Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Progamming), FP (Functional Programming), and FRP (Functional Reac

docs.nestjs.com

 

반응형