IoC(제어 반전), DI(의존성 주입)


IoC (Inversion of Control, 제어 반전)

IoC 는 NestJS 에서 Provider 를 다른 컴포넌트에 주입할 때 사용하는 기술과 같은 것으로 Nestjs 는 프레임워크에 IoC 를 구현하고 있다.

export class UsersController {
    // UsersService 를 컨트롤러에 주입
    constructor(private readonly usersService: UsersService) {
    }
...
}

UserController 는 UserService 에 의존하고 있지만 UserService 객체의 생명주기에는 관여하지 않고 생성자에 주어지는 객체를 가져다가 쓸 뿐이다. 바로 이 역할을 하는 것이 IoC 이다. IoC 때문에 객체의 생명 주기에 신경쓰지 않아도 되서 코드가 간결해진다.


DI (Dependency Injection, 의존성 주입)

DI 는 IoC 컨테이너가 직접 객체의 생명주기를 관리하는 방식이다.

A 객체에서 B 객체 사용 시(=A 객체가 B 에 의존) A 클래스에서 B 클래스를 직접 new 로 생성하여 사용할 수도 있지만 이렇게 되면 B 의 구현체가 변경될 경우 A 는 B 를 직접 참조하고 있으므로 B 가 변경될 때마다 컴파일러는 A 를 다시 컴파일한다. (=컴파일 시간 길어짐)

이러한 상황을 피하기 위해 B 에 대한 인터페이스를 정의하고 A 에서는 그 인터페이스 타입을 이용하면 되지만 해당 인터페이스의 구현체인 B` 등을 직접 생셩해야 하는 것은 여전하다.

이럴 때 IoC 를 사용함으로써 문제를 해결할 수 있다.

export interface Person {
    getName: () => string;
}

@Injectable()
export class Assu implements Person {
    getName() {
        return 'assu';
    }
}

@Injectable()
export class Jane implements Person {
    getName() {
        return 'jane';
    }
}

class MyApp {
    private person: Person;

    constructor() {
        this.person = new Assu();
    }
}

Person 을 구현하는 Assu, Jane 클래스가 있고 각 클래스의 getName() 함수의 구현체가 다르다.
MyApp 클래스는 Person 타입의 멤버 변수를 가지고 있고, 생성자에서 구현체를 생성한다.

위 코드에서 MyApp 클래스를 IoC 를 이용하면 아래와 같다.

class MyApp {
    constructor(@Inject('Person') private p: Person) { }
}

이제 Person 객체는 IoC 에서 관리하게 된다.
Person 은 인터페이스이므로 Person 을 실제 구현한 클래스가 어딘가에 정의되어 있어야 객체 생성이 가능할텐데 이는 Module 에 선언한다.

@Module({
  imports: [EmailModule],
  controllers: [UsersController],
  providers: [UsersService,
      {
          provide: 'Person',
          useClass: Assu
      }
  ],
})

토큰을 ‘Person’ 을 주고 있는데 이 토큰은 Provider 를 가져다쓸 때 @Inject 데커레이터의 인수로 넘겨준 것과 같다.
만일 Jane 으로 구현을 바꾸려면 useClass 를 Jane (=클래스명) 으로 변경하면 된다.


참고 사이트 & 함께 보면 좋은 사이트

본 포스트는 한용재 저자의 NestJS로 배우는 백엔드 프로그래밍을 기반으로 스터디하며 정리한 내용들입니다.






© 2020.08. by assu10

Powered by assu10