singleton
객체의 인스턴스가 오직 1개만 생성되는 패턴
메모리에 인스턴스를 한번만 만들어서 올려놓고 필요할 때 마다 생성하는 패턴이다.
IOC(inversion of control)
제어의 권한을 바꾼다
인스턴스의 생성 및 할당과 해제를 개발자가 하는 것이 아니고, 프레임워크가 이를 맡아주는 것을 말한다.
DI(dependency of injection)
의존성 주입
A가 B를 사용하기 위해 직접 B를 생성하는 것이 아니라, 외부로부터 가져와서 사용한다는 것을 말한다.
❗️ 의존성 주입을 사용하지 않을 경우 단점 ❗️
강한 결합
이라고 한다.memory leak
가 발생할 수 있다. 여기서 잠깐! 근본적으로 생각해보자. 이런 의존성이니 주입이니 이런말이 왜 나온걸까? 아키텍처를 구성할때, 도메인에 따라서 서비스 레이어를 구분하도록 구성하면 구현체의 로직이 변경되도 호출부에서는 해당 내용을 알 필요가 없다. 이로써, 생산성과 재사용성이 좋아지게 된다.
nest는 내부적으로 IOC container 를 이용해서 DI를 관리해준다
ioc container는 @Injectable(), @Module()
데코레이터가 달린 클래스들을 DI대상으로 관리한다. nest 는 typescript 이기 때문에, 타입기준으로 DI를 관리하기 수월하다.
nest는 애초에 싱글스레드이기 때문에, 인스턴스간의 동시성이 발생하지 않는다고 한다.
nestjs 에서는 docs 에서 알 수 있듯이, singleton 을 지향하고 있다. 하지만 무조건 싱글톤으로 구현되는 건 아니다. 아래서 다뤄보겠다.
싱글톤을 구현하기 위해서는 간단하게 설명을 하자면, @Injectable(), @Module()
데코레이터를 사용하면 된다.
@Injectable(), @Module()
데코레이터를 선언하면, 해당 인스턴스를 nest 내장 IOC container 가 관리한다
(데코레이터가 달린 클래스는 타입스크립트가 컴파일시 메타데이터로 어떤 서비스에 의존하고 있는지 명시를 해준다. 그러면 nestjs 가 어떤 의존관계가 있는지 알 수 있다.)
👉 nest 내부의 IOC container 가 @Injectable(), @Module() 데코레이터 클래스를 싱글톤으로 생성하여 DI의 대상으로 관리한다.
socket adapter 를 공부하다가 소켓에 연결하면 즉각 실행하는 afterInit()
이라는 함수를 구현하다가 뭔가 이상함을 봤다.
// ws.gateway.ts
@WebSocketGateway(80)
export class WsKorGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {
constructor(private readonly logger: Logger){
}
@WebSocketServer()
wsServer: Server;
/**
*
* @param server 해당 gateway 가 실행되면 가장 먼저 실행되는 함수 -> handleConnection 보다 먼저 실행된다
*/
afterInit(server: any) {
this.logger.log('kor ws gateway started!')
// 터미널을 보면 이게 두번 실행됨을 알 수 있다.
}
}
// ws.module.ts
@Module({
providers: [WsKorGateway, Logger]
})
export class WsKorModule {
}
// app.module.ts
@Module({
imports: [WsKorModule, WsUsaModule],
controllers: [AppController],
providers: [AppService, WsKorGateway, Logger],
})
export class AppModule {}
위와 같이 소스코드를 구현해 놓고, ws server 를 구동하면 다음과 같은 결과가 나온다.
위 사진을 보면 kor connected!
라는 로그가 두번찍혔는데 이로써 afterInit()
함수가 두번 실행됐음을 알 수 있다.
app module 에서 import 에서 한번 providers 에서 한번, 총 두번 주입을 해줬기 때문에, WsKorGateway 가 두번 실행되서 로그가 두번찍히는건 알겠는데
app module 에서 import 에서 한번, providers 에서 한번, 총 두번 주입을 하지만 한개의 인스턴스를 그냥 두번 주입하는게 아닌가?
nest 는 인스턴스를 싱글톤으로 만든다고 했는데..?
여기서 그냥 아무 decorator 로 싱글톤을 구현하는것이 아닌것을 알 수 있다.
@injectable() @Module()
이 두개로만 싱글톤이 적용되는 것으로 추측된다.
@Controller('payment')
export class PaymentController extends CommonController {
constructor(
@Inject('PAYMENT_SERVICE')
private readonly paymentService: PaymentService,
) {
super();
}
위 같은 경우는 PaymentService 가 변하면 PaymentController 도 같이 컴파일을 다시 해야한다.
@Controller('talk')
export class ChatController extends BaseController {
constructor(
@Inject(CHAT_SERVICE)
private readonly chatService: IChatService,
) {
super();
}
하지만 다음과 같은 경우는 Interface 를 주입받았기 때문에, ChatService의 구현체가 변해도 ChatController 는 컴파일 하지 않아도 된다.
이게 interface 를 사용하는 중요한 이유다 (일종의 추상화)
References
reference 1
reference 2