일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
- OAuth 2.0
- nestjs
- middleware
- 일급 객체
- factory
- java
- Volatile
- lombok
- builder
- 일급 컬렉션
- Spring
- spring security
- Google OAuth
- synchronized
- Dependency Injection
- Today
- Total
HJW's IT Blog
NestJS Document 알아보기 : Providers 본문
Provider
NestJS 에서 Provider는 의존성 주입 시스템의 핵심 개념중 하나이다. Provider 는 Nest 어플리케이션 내에서 인스턴스를 관리하고 주입하는 역할을 담당한다. Provider 는 어떤 특정한 것을 가르키기 보단, Nest 에서 Service, Repository, Factory function, value 혹은 기타 의존성을 제공할 수 있는 클래스나 값이다.
즉 Provider 의 주요 개념은 의존성을 주입할 수 있다 는 것이다.
이를 통해 객체들이 서로 다양한 관계를 만들 수 있으며, 이러한 객체들을 “연결” 하는 작업을 개발자는 Nest의 런타임 시스템에 맡길 수 있다.
이전에 설명했던 Controller는 HTTP 요청을 처리하고 복잡한 작업은 Providers 에게 위임해야 한다.
NestJS 에서 Provider는 @Injectable() 데코레이터를 사용해 정의된다.
import { Injectable } from '@nestjs/common';
@Injectable()
export class MyService {
// 서비스 로직
}
이후, 의존성 주입을 통해 다른 클래스에 주입시켜야 한다. NestJS 는 자동으로 각 클래스의 생성자에 필요한 의존성을 주입해 주기 때문에, 개발자는 직접 인스턴스를 생성하지 않아도 된다.
import { Injectable } from '@nestjs/common';
import { MyService } from './my.service';
@Controller()
export class MyController {
constructor(private readonly myService: MyService) {}
someMethod() {
this.myService.someServiceMethod();
}
}
모듈 내에서도 등록을 해주어야 한다. NestJS에서는 Provider를 모듈 내에서 등록하여 해당 모듈 내에서 자유롭게 사용 가능하다.
import { Module } from '@nestjs/common';
import { MyService } from './my.service';
import { MyController } from './my.controller';
@Module({
providers: [MyService],
controllers: [MyController],
})
export class MyModule {}
💡 NestJS 는 객체 지향적인 방식으로 의존성을 설계하고 조작하기 때문에, SOLID 원칙을 따르는 것을 강력하게 권장한다.
Document Example
이전 포스트에서 생성한 CatController 와 CatInterface를 가져오겠다.
import { Controller, Get, Post, Body } from '@nestjs/common';
import { CreateCatDto } from './dto/create-cat.dto';
import { CatsService } from './cats.service';
import { Cat } from './interfaces/cat.interface';
@Controller('cats')
export class CatsController {
constructor(private catsService: CatsService) {}
@Post()
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
@Get()
async findAll(): Promise<Cat[]> {
return this.catsService.findAll();
}
}
export interface Cat {
name: string;
age: number;
breed: string;
}
이제 CatController 가 사용할 CatService를 생성해야 한다.
import { Injectable } from '@nestjs/common'
import { Cat } from './interfaces/cat.interface';
@Injectable()
export class CatService {
private readonly cats: Cat[] = [];
create(cat: Cat){
this.cats.push(cat);
}
findAll(): Cat[]{
return this.cats;
}
}
Injectable 데코레이터는 CatService 가 Nest 의 IoC 컨테이너에 의해 관리될 수 있는 클래스라는 메타 데이터를 추가한다.
이때, CatService 는 생성자 주입을 통해 주입이 되는데, 이때 private 구문을 사용하면, catService 멤버를 한번에 선언 및 초기화가 가능하다.
constructor(private catsService: CatsService) {}
// private 선언하지 않을 경우
catService : CatService
constructor(catsService: CatsService) {
this.catService = catService;
}
Typescript 의 기능 덕분에 Nest 는 의존성을 쉽게 관리할 수 있다. Type 만으로 의존성이 해결되기 때문이다. 의존성을 수동으로 관리할 필요가 없으며, 타입 안정성이 증가한다. NestJS는 클래스의 생성자에서 요구하는 의존성의 타입을 분석하여 타입의 Provider 를 찾아 인스턴스를 주입한다.
다음은 NestJS에서 Controller에 의존성이 주입되는 과정이다.
- 컨트롤러 정의
- 의존성 선언
- 모듈 등록
- 의존성 해결 : 어플리케이션 시작시 모듈을 로드하고 의존성 관계를 분석한다
- 인스턴스 생성 : IoC 컨테이너가 인스턴스를 생성한다
- 의존성 주입: 컨트롤러가 인스턴스화 될 때, NestJS는 생성자에 인스턴스를 자동으로 주입한다.
- 기본적으로 주입되는 서비스 인스턴스는 싱글톤으로 관리된다.
Scope
Provider는 기본적으로 어플리케이션 라이프사이클과 동기화된 수명(scope) 를 가진다. 어플리케이션이 bootstrap 될 때, 모든 의존성이 해결되어야 하며, 따라서 모든 Provider 가 인스턴스화 된다. 하지만 Provider 의 수명을 요청 단위로 변경할 수도 있다. 이에 대해서는 추후 다루도록 하겠다.
Optional Providers
때로는 해결될 필요가 없는 의존성이 있을 수 있다. 예를 들어 클래스가 설정 객체에 의존하지만 설정이 전달되지 않으면, 기본값을 사용하는 경우가 있다. 이럴때 의존성은 선택사항이 되며, 설정 Provider가 없더라도 오류가 발생하지 않는다.
이를 명시하기 위해 @Optional 데코레이터를 사용할 수 있다.
import { Injectable, Optional, Inject } from '@nestjs/common'
@Injectable()
export class HttpService<T> {
constructor(@Optional() @Inject('HTTP_OPTIONS') private httpClient: HttpClient){}
}
Property-based Injection
지금까지의 모든 예시는 생성자 주입 방식으로, 생성자를 통해 provider를 주입하는 방식이었다. 하지만 특정 상황에서는 Property based injection 이 유용할 수 있다. 예를 들어 상위 클래스가 여러 제공자에 의존하는 경우, 생성자에서 super()를 호출하여 모든 제공자를 하위 클래스로 전달하는 것이 번거로울 수 있다. 이를 피하기위해, @Injectable() 을 프로퍼티 수준에서 사용할 수 있다.
import {Injectable, Inject} from '@nestjs/common'
@Injectable()
export class HttpService<T>{
@Inject('Http_OPTIONS')
private readonly httpClient: T;
}
하지만 어디까지나 ‘가능하다’ 의 범주인것이지, 클래스가 다른 클래스를 상속하지 않는다면 생성자 기반 주입을 사용하는것이 권장된다.
@Injectable()
class ParentService {
@Inject(DependencyService)
protected readonly dependencyService: DependencyService;
someMethod() {
this.dependencyService.doSomething();
}
}
@Injectable()
class ChildService extends ParentService {
// 부모 클래스의 dependencyService를 그대로 사용 가능
@Inject(AnotherDependency)
private readonly anotherDependency: AnotherDependency;
newMethod() {
// 부모의 의존성 사용
this.dependencyService.doSomething();
// 자식 클래스의 추가 의존성 사용
this.anotherDependency.doSomethingElse();
}
}
Provider Registration (제공자 등록)
Provider 를 정의했고, 이를 사용하는 controller를 정의한 후에는, NestJS 에 서비스를 등록해야 한다. 이를 위해 모듈 파일을 수정해야 한다.
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 {}
Manual Instantiation (수동 인스턴스화)
지금까지는 Nest가 대부분의 의존성 해결을 자동으로 처리하는 방식에 대해 설명했다. 하지만 특정 상황에서는 빌트인 의존성 주입 시스템을 벗어나 수동으로 제공자를 가져오거나 인스턴스화해야 할 수 있다. 이러한 두 가지 주제를 간략히 살펴보자.
- Module reference를 사용하여 기존 인스턴스를 가져오거나 제공자를 동적으로 인스턴스화할 수 있다.
- bootstrap() 함수 내에서 제공자를 가져오고 싶을 경우(예: 컨트롤러가 없는 독립형 애플리케이션에서 또는 부트스트래핑 중에 설정 서비스를 사용하기 위해) 이는 독립형 애플리케이션에서 논의된다.
'NestJS' 카테고리의 다른 글
NestJS Document 알아보기 : Exception Filters (0) | 2024.10.20 |
---|---|
NestJS Document 알아보기 : Middleware (0) | 2024.10.20 |
NestJS Document 알아보기 : Module (3) | 2024.10.20 |
NestJS Document 알아보기 : Controller (1) | 2024.10.19 |