저는 주로 Node.JS 를 사용해서 미들웨어라는 게 익숙합니다만, 다른 언어 혹은 프레임워크에서도 미들웨어가 이렇게 적극적으로 쓰이는지는 잘 모르겠습니다. 아무튼 미들웨어는 Node.JS 에서 가장 특징적인 패턴입니다.
미들웨어는 아무래도 Express 에서 용어가 대중화되었고 이 패턴을 적극적으로 사용하고 있지 않나 싶습니다.
function(req, res, next) { ... }
Express 에서 미들웨어는 위와 같이 생겼습니다. 저기서 req 는 Http request, res 는 Http response 이며 next 는 이 미들웨어가 작업을 완료하고 다음 차례의 미들웨어를 트리거할 때 호출되는 콜백 함수입니다. 미들웨어 함수를 정의할 때 저 next 가 빠져있으면 미들웨어로 간주되지 않아 에러가 발생하기도 합니다.
일반적으로 Express 의 미들웨어는 아래와 같은 일들을 합니다.
- 요청 본문의 구문 분석
- 로그 생성
- 세션 관리
- 요청 및 응답 압축 or 해제
- CSRF
딱히 코어 로직과는 상관없는 작업들이라서 일반적으로는 비즈니스 로직에만 집중할 수 있게 하는 서포트의 개념이 강합니다만, 어떻게 구성하느냐에 따라 비즈니스 로직 급으로 꽤 중요한 부분을 맡기도 하는 것 같습니다. 팀에서 사용하는 Express 미들웨어의 예시를 간단히
들어보겠습니다.
// parse_candidate.ts
export async function parseCandidate(req: Request, res: Response, next: NextFunction): Promise<void> {
const candidates = req.body.candidates || [];
const items: Array<{ item: Nullable<Item> }> =
candidates.map(({ itemId }: { itemId: string }) => {
const item = getItem(itemId);
return { item };
});
res.locals.items = items;
next();
}
// route.ts
...
app.post('/items', parseCandidate, send);
...
요청 본문 중 candidates 를 받아서 그 안에 있는 itemId 를 통해 item 을 파싱해 res.locals 에 세팅하고 넘겨주는 미들웨어입니다.
이후의 비즈니스 로직에선 res.locals.items 를 받아서 어떻게 처리할지에 대해서만 집중하고, 그 전처리 과정은 미들웨어에서 처리하는
구조이죠.
위 예시에선 route 에서 /items 에 대한 POST 요청이 들어왔을 때 parseCandidate 만 미들웨어로 넣었습니다만, 실제 코드에선 더 많은 미들웨어를 전처리 과정으로 넣었고 Express 에선 이 부분이 (parseCandidate, parseA, parseB, ..) 와 같이 넣으면 next() 에 의해
파이프라인의 다음 단계가 호출되는 구조로 간단하게 되어 있어 쉽게 사용하고 있습니다.
위에 예시는 Express 에 한정적이긴 하지만, 잠깐 적은 것처럼 미들웨어는 파이프라인에서의 모든 종류 데이터에 대한 전처리 및 후처리를 나타내기도 합니다. 파이프라인에서 데이터가 처리되고 전파되는 방식에 strict 한 규칙은 없고, 아래와 같은 것들을 포함합니다.
- 추가 속성 또는 기능을 사용한 데이터 추가
- 데이터 불변성을 유지하고 처리 결과를 사본으로 반환
Express 라고 다른건 아니지만, 팀에서 파이프라인의 미들웨어로서 사용하고 있는 예시 코드도 잠깐 보도록 하겠습니다.
import fs from 'fs';
import { createGzip } from 'zlib';
import { pipeline } from 'stream';
class A {
...
private gzip(originPath: string, gzPath: string): Promise<void> {
return new Promise<void>((resolve, reject) => {
const gzip = createGzip();
const source = fs.createReadStream(originPath);
const destination = fs.createWriteStream(gzPath);
pipeline(source, gzip, destination, err => {
err ? reject(err) : resolve();
});
});
}
...
}
너무 일부 메서드만 뽑아와서 좀 뭔가 휑하지만.. Node.JS 에서 제공하는 stream 의 pipeline 을 사용한 예시입니다.
작성한 gzip 메서드의 기능은 원래 있는 파일을 gz 으로 압축한 파일을 떨구는 로직인데, Node.JS 에선 이런 pipeline 을 쉽게 사용할 수 있도록 모듈을 제공합니다. readStream 과 writeStream 만 열어서 가운데에 로직만 끼워넣으면 되는 형태죠.
파이프라인 내에서 readStream -> gzip -> writeStream 을 처리하는 형태로 gzip 이 미들웨어의 역할을 하는 것입니다.
Node.JS 에 너무 맞춰진 이번 포스팅이긴하지만, Node.JS 개발자라면 이러한 미들웨어 패턴을 활용할만한 포인트는 많을 것입니다.
'Backend > 디자인 패턴' 카테고리의 다른 글
Template Pattern (템플릿 패턴) (0) | 2022.10.29 |
---|---|
Strategy Pattern (전략 패턴) (0) | 2022.10.24 |
Adapter Pattern (어댑터 패턴) (0) | 2022.10.08 |
Decorator Pattern (데코레이터 패턴) (0) | 2022.10.01 |
Proxy Pattern (프록시 패턴) (0) | 2022.09.25 |