컨트롤러는 요청을 알맞은 기능에 매칭해주고 알맞는 응답을 처리하는 역할을 가진다.
여기서 알맞은 기능을 담당하는 것이 프로바이더이다.
프로바이더에는 주로 서비스(Service), 레포지토리(Repository), 팩토리(Factory)등의 형태가 있다.
@Module({
...
providers: [UsersService]
})
export class UsersModule {}
프로바이더 인스턴스는 모듈에 등록해주면 된다.
Nest개발자들 중 오해하는게 사람이 좀 있는데,
@Injectable
은 이 클래스가 Provider(공급자)라는걸 명시하고 스코프를 지정하기 위해서 쓰이는 데코레이터이다. 인스턴스화는 모듈에 등록을 해야 이루어 지는것이다.
constructor(private usersService: UsersService) {}
위 처럼 생성자에서 의존성을 주입하여 사용하면된다.
프로바이더는 일반적으로 애플리케이션 스코프와 동기화된 스코프를 가진다.
즉 요청별 캐싱, 요청 추적 및 다중 테넌시처럼 스코프를 정해 동작시켜야 할 케이스가 있을때
어떻게 하는지 알아보자.
솔직히 몰라도 된다. 걍 이런게 있다는 것만 알고 자세한 내용은 여기서 살펴보자.
@Injectable({scope:Scope.DEFAULT})
단일 인스턴스이며 애플리케이션 전체 범위에서 공유된다.
애플리케이션이 종료되면 같이 해제되고, 애플리케이션이 부트스트랩(실행)되면 인스턴스화된다.
즉 앱의 시작과 끝을 같이하는 생명주기와 애플리케이션 전체가 스코프 범위라고 볼 수 있다.
Nest 공식사이트에선 Default 즉 싱글톤 인스턴스로 프로바이더를 설정하는걸 가장 추천하고 안전하다고 말한다.
@Injectable({scope:Scope.REQUEST})
//커스텀 프로바이더경우
{
provide: 'CACHE_MANAGER',
useClass: CacheManager,
scope: Scope.TRANSIENT,
}
들어오는 요청마다 인스턴스가 생성되고 요청을 처리하면 해제된다.
사용자의 요청은 단일이 아니라 여러가지 형태가 있을 수 있기때문에 이런게 있다고 이해하면된다.
import { Injectable, Scope, Inject } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { Request } from 'express';
@Injectable({ scope: Scope.REQUEST })
export class CatsService {
constructor(@Inject(REQUEST) private request: Request) {}
}
@Injectable({scope:Scope.TRANSIENT})
이 스코프를 지정한 인스턴스는 공유되지 않는다. 임시(TRANSIENT) 프로바이더를 주입하는 각 컴포넌트는 새로 생성된 전용 인스턴스를 주입받게됩니다. 나는 이걸 써본적이 한번도 없는데,
선배는 정의만 해두고 쓰지않을때 주석대신(?)쓰는걸 보긴했다.
프로바이더는 다양한 기능을 정의하고
다양한 기능 만큼 다양한 방식으로 프로바이더를 설정해야 할 때가 있다.
그 방법을 알아보겠다.
ValueProvider는 provider
와 useValue
속성을 가진다. useValue는 어떤 타입도 받을 수 있기 때문에 useValue구문을 이용하여 외부 라이브러리로 부터 프로바이더를 삽입하거나 실제 구현을 모의 객체로 대체할 수 있다.
// 모의 객체 선언
const mockCatsService = {
};
@Module({
imports: [CatsModule],
providers: [
{
provide: CatsService,
useValue: mockCatsService,
},
],
})
export class AppModule {}
CatsRepository에서 데이터베이스에 연결하기 위해 Connection 객체를 프로바이더로 제공한다고 하면 다음과 같이 임의의 문자열로 선언할 수 있다. 다음 예에서는 'CONNECTION'을 토큰으로 사용한다.
import { connection } from './connection';
@Module({
providers: [
{
provide: 'CONNECTION',
useValue: connection,
},
],
})
export class AppModule {}
CatsRepository에서 정의한 토큰으로 주입받을 수 있다.
@Injectable()
export class CatsRepository {
constructor(@Inject('CONNECTION') connection: Connection) {}
}
밸류 프로바이더는 useValue
속성을 사용했지만, 클래스 프로바이더는 useClass
속성을 사용한다. 프로바이더를 동적으로 구성하고 싶을때 사용한다.
const configServiceProvider = {
provide: ConfigService,
useClass:
process.env.NODE_ENV === 'development'
? DevelopmentConfigService
: ProductionConfigService,
};
@Module({
providers: [configServiceProvider],
})
export class AppModule {}
팩토리 프로바이더는 클래스 프로바이더와 같이 동적으로 구성하고자 할 때 사용하지만,
앞서와는 다르게 타입이 함수로 정의되어 있다.
const connectionFactory ={
provide: 'ASYNC_CONNECTION',
useFactory: async () => {
const connection = await createConnection(options);
return connection;
},
}
@Module({
providers: [connectionFactory],
})
export class AppModule {}
메소드를 정의하는 과정에서 다른 프로바이더가 필요하다면 사용 할 수 있는데,
Inject속성을 통해 정의를 해주면 된다.
const connectionFactory = {
provide: 'CONNECTION',
useFactory: (optionsProvider: OptionsProvider) => {
const options = optionsProvider.get();
return new DatabaseConnection(options);
},
inject: [OptionsProvider],
};
@Module({
providers: [connectionFactory],
})
export class AppModule {}
다른 모듈에 있는 프로바이더를 쓸려면 해당 모듈에서 내보내기를 해줘야한다.
const connectionFactory = {
provide: 'CONNECTION',
useFactory: (optionsProvider: OptionsProvider) => {
const options = optionsProvider.get();
return new DatabaseConnection(options);
},
inject: [OptionsProvider],
};
@Module({
providers: [connectionFactory],
exports: [connectionFactory],
})
export class AppModule {}
해당 모듈범위에서 외부 프로바이더를 가져와 쓰고싶다면 imports를 이용하면 된다.
const connectionFactory = {
provide: 'CONNECTION',
useFactory: (optionsProvider: OptionsProvider) => {
const options = optionsProvider.get();
return new DatabaseConnection(options);
},
inject: [OptionsProvider],
};
@Module({
imports:[HelloService],
providers: [connectionFactory],
exports: [connectionFactory],
})
export class AppModule {}