[NestJS] 데이터 요청과 응답 사이의 Request lifecycle
NestJS를 쓰면서 갑자기 느낀 게 있었다. 바로 NestJS 내부에서 데이터의 흐름이 어떻게 되는지 궁금했다. Service단 에서의 데이터 흐름뿐 아니라 클라이언트에서 데이터를 요청한 이후 데이터를 응답할 때 까지의 흐름을 알고 싶었다. 그래서 공식 문서를 보며 정리하기로 했다. 공식 문서는 Request lifecycle이라고 하는데, 왠지 Data lifecycle이라는 이름이 더 맞는다.
참고로 express는 use 메서드에 온갖 미들웨어를 넣어버리는데, 이 때 데이터 흐름을 개발자가 직접 제어해 주어야 한다. express는 자유도는 매우 높은 프레임워크이나, 견고하고 가독성 좋은 코드를 만들기 힘들다.
데이터 흐름
1. 가드
미들웨어 다음으로 오는 가드는 데이터가 들어올 때 처음으로 데이터를 판별하는 곳이다. 가드는 등록된 사용자 판별, 사용자의 역할 판별 등의 역할을 맡는다. 말 그대로 문지기인 셈이다. 아래 코드에서 UseGuards 데코레이터를 사용하는 곳이다.
@UseGuards(Guard1, Guard2)
@Controller('cats')
export class CatsController {
constructor(private catsService: CatsService) {}
@UseGuards(Guard3)
@Get()
getCats(): Cats[] {
return this.catsService.getCats();
}
}
2. 인터셉터
공식 문서에서는 AOP (Aspect Oriented Programming)에서 영감을 받았다고 한다. 아래와 같은 기능을 수행할 수 있다.
- 메서드 실행 전후 추가 로직 바인딩
- 함수 반환 결과 반환
- 함수 예외 반환
- 기본 기능 동작의 확장
- 특정 조건에 따라서 기능 재정의 (예 : 캐싱)
공식 문서의 예시는 인터셉터를 사용하여 로깅하는 것으로 보여준다. 여기서 인터셉터는 응답 또는 반환 데이터를 중간에 조작할 수 있으며, callHandler에서는 rxjs의 Observable값을 반환한다. 해당 응답 스트림에서 추가 작업을 하는 것 같아 보이는데, 이걸 또 잘 다루려면 rxjs같은 stream 함수형 프로그래밍 라이브러리를 따로 공부해야 할 거 같다.
3. 파이프
데이터 이동 시 중간에 데이터를 변환(문자열 -> 숫자) 하거나 유효성 검사를 할 수 있게 한다. NestJS에서는 바로 사용할 수 있는 파이프를 9개나 주지만! 실사용 하다 보면 매우 적다. 그래서 보통 class-validator 같은 라이브러리를 가져와서 유효성 검사를 한다.
4. 실제 로직
이제서야 Controller를 지나서 Service 안에 있는 로직이 실행이 된다.
5. 응답 인터셉터
인터셉터는 앞에서는 요청받는 데이터에 대해서 작업했다면, 이번에는 응답하는 데이터에 대해서 한번 더 거쳐간다.
예외) 예외 필터
보통 에러를 던짐 (throw new InternalServerException 같이). 이거는 전역으로 적용이 되므로, 특정 단계로 볼 수 없을거 같아서 예와로 처리해 두었다.
그림으로 정리
위의 내용을 토대로 흐름도를 그리면 다음과 같아 보인다.
참고자료
https://docs.nestjs.com/faq/request-lifecycle