Nest provider

백엔드·2023년 7월 4일
0

NestJS

목록 보기
4/7

provider

프로바이더는 Nest의 기본 개념입니다. 많은 기본 Nest 클래스는 서비스(Service), 레파지토리, 팩토리, 헬퍼 등등의 프로바이더로 취급될 수 있습니다. 프로바이더의 주요 아이디어는 의존성을 주입할 수 있다는 점입니다. 이 뜻은 객체가 서로 다양한 관계를 만들 수 있다는 것을 의미합니다. 그리고 객체의 인스턴스를 연결해주는 기능은 Nest 런타입 시스템에 위임될 수 있습니다.

제어 역전

제어 역전을 한 마디로 표현한다면 나 대신 프레임워크가 제어한다 입니다. 제어 역전을 설명하기 위해서는 의존성이라는 개념을 알아야한다.

const sword = new Sword();

Warrior클래스에서 Sword클래스를 인스턴스화 했습니다. 여기까지는 별 문제 없습니다. 필요한 클래스를 생성해서 사용하는게 무슨 문제가 있겠습니까. 다만 기획팀에서 다음 업데이트때 이제 전사는 칼 뿐만 아니라 몽둥이도 사용할 수 있다고 지령이 내려옵니다. 이미 코드 백 만군데나 Sword()를 박아뒀는데 이럴 어찌하면 좋습니까.

좋은 객체지향 설계는 구체적인 개념에 의존하지 말고 추상적 개념에 의존해야 합니다. 위 코드에서 new를 사용하면 Sword와 Sword를 생성하는 Warrior 사이에 의존성이 생깁니다. 정확히는 Warrior가 Sword에 의존하게 되지요. 이렇게 직접적이고 구체적으로 클래스를 인스턴스화하는 행동은 바람직하지 않습니다.

이럴때 인터페이스가 혜성처럼 나타납니다. 프로그래밍에서 인터페이스는 규약입니다. 인터페이스를 구현하려면 인터페이스가 원하는 규약을 따라야 합니다. 반대 급부로 프로그래머는 내가 호출하는 클래스가 무엇인지 정확하게 알 필요가 없습니다. 다만 특정 기능이 동작 가능하다는 사실만 알고 개발하면 됩니다. 이제 구조를 좀 바꿔보죠. 하는 김에 전사 말고 궁사나 마법사 확장도 염두에 두고 지원하도록 해봅시다.

interface Weaponable {
  swing(): void;
}

interface Playable {
  attack(): void;
}

class Warrior implements Playable {
  private Weaponable weapon;

  constructor Warrior(private readonly Weaponable _weapon) {
    weapon = _weapon;
  }

  public void attack() {
    weapon.swing();
  }
}

class Mongdungee implements Weaponable {
  public void swing() {
    console.log('Mongdungee Swing!');
  }
}

몽둥이 쥔 전사 클래스를 인스턴스화 시켜보겠습니다.

Warrior warrior = new Warrior(new Mongdungee());

참 쉽죠? 하지만 클래스 계층 구조가 복잡한 프로그램에서 직접 저 몽둥이를 넘겨야 한다거나, 여러 전사에게 같은 몽둥이를 넘기거나 한다는 상황에서는 그닥 좋지 않습니다. 이 경우 제어 역전을 사용합니다. Nest는 제어 역전을 추상화해서 그 동작이 잘 보이지 않기 때문에 typedi를 사용해서 이를 구현해보겠습니다.

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

// Weaponable, Playble 은 위와 같음

@Service()
class Mongdungee implements Weaponable {
  public void swing() {
    console.log('Mongdungee Swing!');
  }
}

@Service()
class Warrior implements Playable {
  // 아래 코드 중요!
  constructor(private readonly weapon: Mongdungee) {}

  public void attack() {
    this.weapon.swing();
  }
}


const playerInstance = Container.get<Warrior>(Warrior);
playerInstance.attack();  // "Mongdungee Swing!"

코드 어디에서도 new가 없습니다. 하지만 잘 동작함을 확인할 수 있습니다. 이는 typedi의 Container라는 친구가 알아서 클래스의 인스턴스를 생성했기 때문입니다. 이처럼 제어권을 내가 아닌 프레임워크에게 넘기는 것이 제어 역전입니다.

여기서 라이브러리와 프레임워크의 결정적인 차이가 발생합니다. 라이브러리는 내가 짠 코드에서 필요할 때 라이브러리를 실행시킵니다. 즉 라이브러리는 제 코드의 소비자가 될 수 없습니다. 반면 프레임워크는 제 코드의 소비자가 될 수 있습니다. 프레임워크는 내가 짠 코드가 필요할 때 알아서 실행시키게 되지요.

의존성 주입(DI, Dependency Injection)

제어 역전(이하 IoC)은 나 대신 프레임워크가 제어한다라면 의존성 주입(이하 DI)은 프레임워크가 주체가 되어 네가 필요한 클래스 등을 너 대신 내가 관리해준다는 개념이라고 생각하시면 됩니다.

앗? DI는 이미 제어 역전 설명할때 나왔죠. Warrior 클래스의 생성자에서 new없이 선언만 했는데도 마치 인스턴스처럼 사용할 수 있었던 코드 말이죠! 이쯤되면 많은 분들이 제어 역전(IoC)과 의존성 주입(DI)이 헷갈리곤 합니다. 멀리서 보면 DI보다 IoC가 더 크고 추상적인 개념입니다. IoC는 추상적이기 때문에 이를 구현한게 바로 DI이며 이는 제어 역전의 구현체 중 하나입니다. 그래서 DI를 통해 IoC를 구현했다는 말이 나옵니다. Nest는 DI를 통해 IoC를 구현한 프레임워크입니다.

공식문서

프로바이더는 Nest의 기본 개념입니다. 많은 기본 Nest 클래스는 서비스(Service), 레파지토리, 팩토리, 헬퍼 등등의 프로바이더로 취급될 수 있습니다. 프로바이더의 주요 아이디어는 의존성을 주입할 수 있다는 점입니다. 이 뜻은 객체가 서로 다양한 관계를 만들 수 있다는 것을 의미합니다. 그리고 객체의 인스턴스를 연결해주는 기능은 Nest 런타입 시스템에 위임될 수 있습니다.

약간 이해될듯 말듯 하시죠? 한 마디로 하자면 Warrior의 Mongdungee가 바로 프로바이더입니다. 어떤 컴포넌트가 필요하며 의존성을 주입당하는 객체를 프로바이더라고 생각하시기 바랍니다.

ref

profile
백엔드 개발자

0개의 댓글