지식&개념

IoC, DI [Nest.js]

muyeon 2023. 8. 13. 16:33

provider

 

- provider 는 nest.js 의 기본 개념

- 대부분의 nest 클래스는 service, repository, factory, helper 등 provider 로 취급 될 수 있다.

- provider 의 주요 아이디어는 dependency 로 주입할 수 있다.

- dependency 로 주입 할 수 있다는 의미는 object 가 다른 object 와 다양한 관계를 만들 수 있고, 객체의 instance 를 "wiring up" 기능은 NestJs runtime system 에 위임될 수 있다.

 


IoC

 

Dependency Injection(DI) 는 IoC 기술로 자신의 코드로 종속성을 인스턴스화 하는 대신 IoC 컨테이너(NestJS 런타임 시스템)로 위임한다.

제어의 역전이란 객체는 객체가 의존하는 다른 객체를 스스로 생성하지 않고 외부로 부터 얻음을 의미한다. 결합성을 느슨하게 하고 테스트, 재사용성을 용이하게 하는 패턴이다.

 

- 개발자가 제어할 영역을 NestJs 에게 넘겨주는 것

- NestJS 는 providers 사이의 관계를 해결해 주는 내장된 Inversion of control("IoC") container 를 가지고 있다.

- DI 를 구현하기 위해서는 IoC 컨테이너 기술이 필요하다. IoC 는 provider 를 다른 컴포넌트에 주입할 때 사용하는 기술이다, Nest 는 프레임워크에 IoC 를 구현하고 있다.

 

export class UserContoller {
	constructor(private readonly usersService : UsersService) {}
}

- UserController 는 UserService 에 의존하고 있다.

- 하지만 UsersService 객체의 라이프 사이클에는 전혀 관여하지 않는다.

- 단지 controller 의 생성자에 주어진 객체를 가져다 쓰고 있다.

- 이 역할을 하는 것이 IoC(Inversion of control)

- IoC 의 도움으로 객체의 라이프 사이클에 신경 쓰지 않아도 된다.

 

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

 

1. 직접적인 의존성 : A 객체는 B 객체를 필요로 하므로, A 는 B 를 직접 생성하고 사용한다.

2. 문제점 : B 객체가 변경되면 그 변경은 A에게도 영향을 준다. 따라서 B 가 바뀌면 A 도 다시 컴파일 해야 한다. 이렇게 되면 시스템 전체에서 많은 부분이 자주 다시 컴파일 될 수 있어서 비효율 적이다.

3. 해결책 - 인터페이스 도입 : B 에 대한 '인터페이스' 를 만든다. (예 : IB ) A는 이제 B 객체 자체가 아닌 B 의 인터페이스 IB 를 참조한다. 이렇게 하면 B 의 구현이 변경되더라도 A 는 영향을 받지 않게 된다.

4. 하지만 여전히 문제 : A 는 여전히 B 의 구현체(실제 동작하는 B 객체) 를 직접 생성해야 한다.

5. IoC(제어의 역전)를 통한 해결 : IoC 는 이 문제를 해결하기 위한 방법이다. A 가 B 의 구현체를 직접 생성하는 대신, 외부에서 (주로 프레임워크나 컨테이너에서) A에게 B 의 구현체를 주입해주는 방식이다. 이렇게 되면 A는 B의 구현체를 직접 알 필요가 없게된다.

 

요약하면 A와 B 가 서로 밀접하게 연결이 되어 있으면, B에 변경이 생기면 A 도 영향을 받게 된다. 이를 인터페이스를 통해 해결할 수 있지만, 완벽한 해결은 아니다. 완벽한 해결을 위해 IoC 를 사용하여 A와 B의 의존성을 완전히 분리하게 된다.

 

// IoC 를 사용 안한 예시

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

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

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

class MyApp {
	private person: Person;
    constructor() {
    	this.person = new Dexter();
    }
}

 

// IoC 를 사용한 예시

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

- Person 객체의 관리는 IoC 가 담당한다.

- Person 은 인터페이스이기에 Person 을 실제 구현한 클래스를 module 에 선언해야 객체를 생성할 수 있다.

 

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

 


DI

 

- dependencies 는 클래스가 동작하이 위해 필요한 서비스나 객체를 의미한다.

- DI 는 class 가 의존성 객체를 외부에 요청하고 외부에서 인스턴스를 생성해서 주입하는 디자인 패턴

- @Injectable() 데코레이터는 이 클래스를 DI system 에 활용하겠다는 것을 의미한다.

- @Injectable() 데코레이터는 UserService 가 Nest IoC 컨테이너에서 관리할 수 있는 클래스임을 선언하는 메타 데이터를 첨부

- @Injectable() 만일 userService 에 이 데코레이터를 사용하면 다른 어떤 Nest component 에서도 주입할 수 있는 provider 가 된다. 별도의 스코프를 지정하지 않으면 singleton 인스턴스가 된다.

 

contructor(private userRepository : UserRepository)

- NestJS 는 일반적으로 DI 로 알려진 디자인 패턴을 기반으로 구축되었다.

- NestJS에서는 TypeScript 기능 덕분에 dependency가 유형별로 해결되기에 매우 쉽게 관리할 수 있다.

- UserRepository 를 UserService 에 DI 를 하려면 constructor 에 접근 제어 지시자를 활용해 초기화를 해주면 된다.

- private 을 사용하면 동일한 위치에서 즉시 userRepository 멤버를 선언하고 초기화 할 수 있다.

- 위와 같은 코드를 생성자 기반 주입 -> provider 는 생성자 메서드를 통해 주입되기 때문이다.

- 특정한 경우 속성 기반 주입(Property-based-injection)이 유용 -> 최상위 클ㄹ스가 하나 또는 여러 프로바이더에 의존하는 경우 생성자의 하위 클래스에서 super() 를 호출하여 모든 프로바이더를 전달하는 것은 매우 쉽지 않다. 이를 방지하기 위해서 property 수준에서 @Inject() 데코레이터를 사용한다.

 

import { UserRepository } from './../repository/user.repository';
import { PrismaService } from './../prisma/prisma.service';
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';

@Module({
  controllers: [UserController],
  providers: [UserService, PrismaService, UserRepository],
})
export class UserModule {}

provider 로 UserService, PrismaService, UserRepository 를 정의하고 controller 가 있으니 injection 을 수행할 수 있게 service 를 Nest 에 등록해야 한다. @module() 데코레이터의 providers 배열에 service 들을 추가해서 이를 수행한다. 이렇게 해서 NestJS 는 userController class 의 dependency 를 해결할 수 있다.

 

의존성 주입은 OOP 에서 많이 활용하고 있는 기법이다. 의존성 주인을 이용하면 객체를 생성하고 사용할 때 관심사를 분리할 수 있다. 이는 코드 가독성과 재사용성이 높은 SW 를 만들게 도와준다.

 


참고 링크

 

https://m.blog.naver.com/fbfbf1/222620699725

 

[NestJS] IoC, DI, Singleton

provider - provider는 Nest의 기본 개념 - 대부분의 Nest 클래스는 service, repository, factory ,help...

blog.naver.com

https://velog.io/@jeong3320/NestJsIoC-%EC%99%80-DI

 

[NestJs]IoC 와 DI

제어의 역전이란 객체는 객체가 의존하는 다른 객체를 스스로 생성하지 않고 외부로 부터 얻음을 의미한다.커플링을 느슨하게하고 테스트,재사용성을 용이하게 하는 패턴이다.IoC패턴의 구체적

velog.io

https://any-ting.tistory.com/140

 

[OOP] 제어의 역전 IoC(Inversion of Control)와 의존관계 주입 DI(Dependency Injection)

- 개요 안녕하세요. 이번 시간에는 제어의 역전 IoC와 의존관계 주입 DI에 대해 알아보겠습니다. 객체 지향 프로그래밍 공부를 하면 반드시 나오는 하나의 개념입니다. 이해하시는데 도움이 되면

any-ting.tistory.com

https://blog.donggeun.co.kr/view/640a024cc9ee667c939ec402

 

https://blog.donggeun.co.kr/view/640a024cc9ee667c939ec402

 

blog.donggeun.co.kr