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로 배우는 백엔드 프로그래밍을 기반으로 스터디하며 정리한 내용들입니다.