NestJS middleware는 어떻게 작동할까 (5)

1. 들어가며


  • 이제 4개의 긴 글을 총정리하고 마무리하려고 합니다.

2. 큰 흐름


flowchart TD A[NestFactory.create] --> B{nestApplication} B --> C[nestApplication.init] C --> D[nestApplication.registerModules] D --> F{middlewareModule} F --> NN[middlewareModule.resolver.resolveInstances] NN --> II{resolver} II --> JJ[resolver.resolveMiddlewareInstance] JJ --> KK{resolver} KK --> LL[injector.loadMiddleware] LL --> MM[injector.loadInstance] F --> G[middlewareModule.register] G --> H[middlewareModule.resolveMiddleware] H --> J[middlewareModule.loadConfiguration] J --> BB{middlewareBuilder} BB --> |middlewareBuilder 매개변수| CC[instance.configure] CC --> DD[middlewareBuilder.apply] DD --> |MiddlewareBuilder.ConfigProxy| EE[MiddlewareBuilder.ConfigProxy.exclude] EE --> |MiddlewareBuilder.ConfigProxy| FF[MiddlewareBuilder.ConfigProxy.forRoutes] FF --> |MiddlewareBuilder| GG[MiddlewareBuilder.build] GG --> |middlewareCollection Array| HH[middlewareContainer.insertConfig] J --> K[middlewareModule.resolveInstances] D --> E[nestApplication.registerRouter]
  • nest가 init되면서 미들웨어에 대한 구성이 저장되고 저장된다.
  • registerModules를 통해 미들웨어 모듈 정보가 저장되고 구성되고, registerRouter를 통해 실제로 미들웨어들이 구성된 Router들에 적용된다.

3. 미들웨어를 구성하고 인스턴스를 생성하는 부분


3.1 nestApplication.registerModules


  • 우선 middlewareModule을 통해 미들웨어 모듈을 등록합니다.
  • 모든 모듈을 돌면서 미들웨어에 대한 정의가 있는지 확인하고, 있다면 해당 미들웨어의 구성을 로드하고 처리하고 미들웨어 컨테이너에 정보를 저장합니다.
  • 그리고 모든 미들웨어에 대한 인스턴스들을 resolve합니다. (래퍼 클래스)

3.2 nestApplication.registerModules - loadConfiguration


  • instance(모듈)에 정의된 미들웨어를 읽고, middlewareBuilder를 통해 정의한대로 구성하여 미들웨어 정보를 반환합니다.
@Module({
  imports: [CatsModule],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer // middlewareBuilder
      .apply(LoggerMiddleware) // apply -> configProxy 리턴
      .exclude('cats') // exclude -> configProxy 리턴
      .forRoutes('*'); // forRoutes -> middlewareBuilder 리턴
  }
}
  • 구성된 메서드를 따라 MiddlewareBuilder 클래스에서 메서드를 체이닝해서 호호출합니다.
  • apply는 가장 먼저 호출되어야하고, forRoutes가 가장 마지막으로 호출되어야하는데요, 이는 반환하는 값들을 보면 알 수 있습니다.
  • 해당 구성이 끝나면 build()를 통해서 middlewareCollection를 배열로 반환하여 미들웨어 컨테이너에 저장합니다.
    • MiddlewareBuilder.middlewareCollection → apply, exclude등의 정보가 저장된 미들웨어를 클래스로 만들어서 저장
    • MiddlewareContainer.middleware → 모듈(최상단 key) - 미들웨어의(key) - 인스턴스 래퍼(value)들을 저장하는 이중 Map
    • MiddlewareContainer.configurationSets → 모듈의(key) - 미들웨어들의 설정(apply, exclude..) (value)를 저장하는 Map
  • 인스턴스 래퍼는 인스턴스가 아니다!

3.3 nestApplication.registerModules - resolver.resolveInstances


  • 모든 미들웨어 인스턴스를 resolve합니다.
  • resolver.resolveMiddlewareInstance를 통해 인스턴스를 생성하고, 종속성을 주입합니다.
    • injector.loadMiddleware를 통해 프로토타입 기반 새로운 인스턴스를 생성합니다.
    • injector.loadInstance를 통해 인스턴스를 로드하고 종속성을 주입(생성자 파라미터 생성)합니다.
  • 해당 과정을 통해서 미들웨어 인스턴스가 만들어집니다.

4. 생성된 미들웨어에 router에 실제 적용하는 부분


4.1 nestApplication.registerRouter


flowchart TD II[nestApplication.registerRouter] --> JJ[nestApplication.registerMiddleware] JJ --> KK{middlewareModule} KK --> LL[middlewareModule.registerMiddleware] LL --> MM[middlewareModule.registerAllConfigs] MM --> NN[middlewareModule.registerMiddlewareConfig] NN --> OO[middlewareModule.registerRouteMiddleware] OO --> PP[middlewareModule.bindHandler] PP --> QQ[middlewareModule.registerHandler]
  • 이제 생성된 미들웨어 모듈을 모두 적용하는 부분입니다.
  • nestApplication.registerRouter 를 통해 nestJS 애플리케이션의 라우터를 등록하는데요, 들어오는 http요청을 정의된 경로에 따라 적절한 컨트롤러와 핸들러로 전달하는 역할을 합니다.
  • nestApplication.registerMiddleware 라우터를 등록한 후, 애플리케이션은 미들웨어를 등록합니다.
  • middlewareModule.registerAllConfigs / middlewareModule.registerMiddlewareConfig를 통해 모든 미들웨어 설정을 등록합니다.
    • middlewareModule.registerRouteMiddleware 를 통해 collection의 인스턴스 래퍼를 가져와서 구성 정보들을 그래프 인스펙터에 적용합니다: 의존성을 시각화하고 관리하는 용도
      • middlewareModule.bindHandler / middlewareModule.registerHandler를 통해 실제로 지정된 경로, 호출에 대해 proxy를 설정해서 미들웨어를 라우트에 적용합니다.

5. 마무리


  • nestJS에서는 middleware보다는 gaurd를 권장하지만, middleware도 핵심 개념인 만큼 깊게 공부해보았습니다.
  • 미들웨어가 구성되고 적용되는 절차를 따라가며 미들웨어에 대해서도 잘 알게되었지만, nestApplication에 대해서도 알게 되어서 좋았습니다.
  • 추후 다른 개념들을 살펴볼때도 결국 해당 클래스에서 모든 것들이 처리되어서 같은 메소드(e.g injector.loadInstance), 같은 원리로 처리되기 때문에 이해가 더 빠를 것 같습니다.