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를 설정해서 미들웨어를 라우트에 적용합니다.
- middlewareModule.registerRouteMiddleware 를 통해 collection의 인스턴스 래퍼를 가져와서 구성 정보들을 그래프 인스펙터에 적용합니다: 의존성을 시각화하고 관리하는 용도
5. 마무리
- nestJS에서는 middleware보다는 gaurd를 권장하지만, middleware도 핵심 개념인 만큼 깊게 공부해보았습니다.
- 미들웨어가 구성되고 적용되는 절차를 따라가며 미들웨어에 대해서도 잘 알게되었지만, nestApplication에 대해서도 알게 되어서 좋았습니다.
- 추후 다른 개념들을 살펴볼때도 결국 해당 클래스에서 모든 것들이 처리되어서 같은 메소드(e.g injector.loadInstance), 같은 원리로 처리되기 때문에 이해가 더 빠를 것 같습니다.
