Nest.js의 Pipe는 굳이 비교하자면 Spring의 Converter 또는 Formatter의 그것과 같다.
하지만 Middleware나 helper가 있는데 왜 Pipe라는 개념까지 도입한 것일까라는 의문이 생긴다.
공식문서의 개념에서 Pipe는 @injectable
decorator을 사용하는 JS class 라고 설명하고 있다. 즉, Provider과 기본 개념이 동일하다.
그렇다면 먼저 injectable
이 무엇인지 알아보자.
Injectables
A모 프레임웍에서 에서 파생된 injectable
은 간단히 말해 Dependency Injection 패턴을 적용시키겠다는 말이다.
Nest는 Provider 패턴으로 쉽게 dependency를 관리 및 공유할 수 있는데,
한 번의 주입으로 만들어진 의존성 인스턴스를 하위 모듈에서 쉽게 접근할 수 있다는 장점이 있다.
React Context Provider ∝ Nest Module Provider
하위 객체에게 의존성을 주입하는 것은 비슷하다.
이는 인스턴스 한 개만 관리하기 때문에 디버깅을 간단하게 해줄뿐만 아니라 메모리 누수를 막을 수 있다는 매우 큰 이점을 가져다 준다.
다음과 같은 예를 보자.
@Injectable()
export class ExampleService {
constructor(private memoryLeak = new ArrayBuffer(10000)) {}
}
// ExampleService 인스턴스가 생성될 때마다 10000byte 메모리 누수를 일으킨다.
@Module({
providers: [ExampleService],
})
export class ExampleModule {}
// 이를 `Injectable`화 하여 주입하면 Nest는 인스턴스를 재사용하게 된다.
이같이 한 번의 Provider 주입만으로 injectable
을 하위 모듈간 쉽게 위임하고 재활용할 수 있다.
<Dependency Injection>
명시적 injectable
을 통한 DI패턴은 유용하지만, 여기까지만 보면 그냥 필요에 따라 Provider에 헬퍼나 팩토리 따위를 담아 사용하는 방법이 떠오른다.
그렇다면 다시 처음으로 돌아가 Pipe는 타 헬퍼과 다른 점이 무엇일까?
Pipe vs. the World
공식문서를 인용하자면 Pipe는 컨트롤러 handler의 매개변수 전처리를 도와주는 특수한 helper라고 정의할 수 있다. 다시 말해 input으로 들어오는 데이터를 지정된 파이프를 거쳐 들어오게끔 하는 것이다.
이는 Middleware의 역할이 아닌가라고 볼 수 있지만 Middleware은 Request에 항상 관여한다면, Pipe는 특정 route handler에 붙어 필요한 데이터값만 선별해 다룬다는 점에 차이가 있다.
이렇게 Pipe를 거친 input을 handler에 전달할 경우 각 단일책임원칙을 위배하지 않음과 동시에 알맞게 정제된 반환값을 받을 수 있다.
또한, Pipe 역시 재사용 가능한 injectable
이므로 어느 라우터에 갖다 붙여도 된다는 장점이 있다.
<Request flow - Pipe는 모두 재사용 가능하다>
Pipe 또한 당연하게도 exception filter의 혜택을 누릴 수 있다. 특정 매개변수의 에러를 이곳에서 처리하자.
Pipe use-case
Pipe 역할은 이제 알디시피 '맞춤형' 필터라고 볼 수 있으며 평소 middleware이 하는 역할들을 pipe에게도 맡길 수 있을 것이다.
태생이 이렇다 보니, Nest 프레임웍은 자주 쓰이는 기본 Pipe들을 제공한다.
실전으로 들어가 REST API에서 /person/3
endpoint에 요청을 받았다고 생각해보자.
@Controller('person')
export class PersonController {
@Get(':id')
async getPerson(@Param('id') id: number) {
// return a matching person
}
}
이 때, id
가 항상 number
로만 들어오면 괜찮지만 만약 3a
가 들어올 경우 Pipe를 제공해 변환해주어야 한다.
// ParseIntPipe에 변환 과정을 위임한다.
@Get(':id')
async getPerson(@Param('id', ParseIntPipe) id: number) {
// return a matching person
}
이처럼 Pipe를 사용해 아주 간단히 getPerson
handler의 관심사 분리를 할 수 있다.
Pipe용으로 사용할 수 있는 매개변수는
@Param
,@Body
,@Query
가 있다.
위 예시는 기본적인 Pipe 사용 방법을 제시했지만, 유효성 검증 및 입맛에 따라 커스텀 Pipe도 얼마든지 만들 수 있다.
Top comments (0)