Interceptor/Middleware 패턴
TL;DR
- Interceptor/Middleware 패턴의 핵심 개념을 빠르게 파악할 수 있다.
- 배경과 이유를 통해 왜 필요한지 맥락을 이해할 수 있다.
- 특징과 상세 내용을 통해 실무 적용 포인트를 확인할 수 있다.
1. 개념
Interceptor/Middleware 패턴의 핵심 정의와 문제 공간을 간단히 정리한다.
2. 배경
이 주제가 등장한 기술적·조직적 배경과 기존 접근의 한계를 설명한다.
3. 이유
왜 지금 이 방식을 채택해야 하는지, 기대 효과와 트레이드오프를 함께 정리한다.
4. 특징
핵심 동작 방식, 장단점, 적용 시 주의점을 빠르게 훑을 수 있도록 요약한다.
5. 상세 내용
Interceptor/Middleware 패턴
작성일: 2026-03-11 카테고리: Backend / Design Pattern / Architecture 포함 내용: Interceptor, Middleware, Filter, Chain of Responsibility, Pipes and Filters, POSA2, GoF, Express.js, Spring HandlerInterceptor, Servlet Filter, Koa Onion Model, AOP, Decorator, 횡단 관심사, Cross-Cutting Concerns, gRPC Interceptor, Service Mesh, Sidecar
1. 개요
┌─────────────────────────────────────────────────────────────────┐
│ Interceptor/Middleware 패턴이란? │
│ │
│ 정의: │
│ A에서 B로 이동하는 요청/응답을 중간 경로에서 가로채어 │
│ 횡단 관심사(Cross-Cutting Concerns)를 처리하는 아키텍처 패턴 │
│ │
│ 비유: 공항 보안 검색대 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 승객(요청) ──► 신분확인 ──► 짐검사 ──► 보안검색 ──► 탑승│ │
│ │ │ │
│ │ 각 검색대는: │ │
│ │ ├── 독립적으로 동작 (관심사의 분리) │ │
│ │ ├── 순서가 중요 (신분확인 → 짐검사 순서) │ │
│ │ ├── 통과/차단을 결정 (필터링) │ │
│ │ └── 승객은 검색대 내부 로직을 모름 (투명성) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 핵심 가치: │
│ ├── 인증, 로깅, 압축, CORS 등을 비즈니스 로직에서 완전 분리 │
│ ├── 기존 코드 수정 없이 새로운 관심사 추가 (개방-폐쇄 원칙) │
│ └── 프레임워크마다 이름만 다를 뿐 본질은 동일 │
│ │
│ 프레임워크별 이름: │
│ ├── Java Servlet → Filter │
│ ├── Spring MVC → HandlerInterceptor │
│ ├── Express.js → Middleware │
│ ├── Django → Middleware │
│ ├── Rails → before_action (구 before_filter) │
│ ├── ASP.NET MVC → Action Filter │
│ ├── gRPC → Interceptor │
│ └── NestJS → Middleware + Interceptor + Guard + Pipe │
│ │
└─────────────────────────────────────────────────────────────────┘
2. 용어의 어원과 기원
2.1 “Intercept”의 라틴어 어원
┌─────────────────────────────────────────────────────────────────┐
│ Intercept의 어원 │
│ │
│ 라틴어 분해: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ inter- (사이에, between) │ │
│ │ + │ │
│ │ capere (잡다, to seize) │ │
│ │ = │ │
│ │ intercipere (통과 중에 가로채다) │ │
│ │ │ │
│ │ Proto-Indo-European 어근: *kap- "붙잡다" │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 로마 군사 용어에서 유래: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ interceptores = 적의 전령을 매복 공격하여 │ │
│ │ 군사 문서를 탈취하는 기병대 │ │
│ │ │ │
│ │ [아군 진영] ←─── interceptores ───X─── [적 전령] │ │
│ │ (매복 기병대) (문서 탈취) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 영어 명사 interceptor 최초 기록: 1590년대 │
│ 군사 항공 (1930년대~): 적기를 차단하는 고속 전투기 │
│ │
│ 핵심 의미 (소프트웨어에서도 동일): │
│ "A에서 B로 이동 중인 무언가를 중간 경로에서 가로채는 행위" │
│ │
└─────────────────────────────────────────────────────────────────┘
2.2 세 가지 핵심 용어의 관계
┌─────────────────────────────────────────────────────────────────┐
│ Middleware / Interceptor / Filter의 관계 │
│ │
│ Middleware (미들웨어) │
│ ├── 의미: 위치 개념 (가장 넓은 추상 계층) │
│ ├── 기원: 1968년 NATO Software Engineering Conference │
│ │ Alex d'Agapeyeff가 최초 사용 │
│ ├── 원래 뜻: OS와 응용 프로그램 사이에 위치하는 소프트웨어 │
│ └── 시대별 변화: │
│ ├── 1968: OS-앱 사이의 계층 │
│ ├── 1990s: 분산시스템 인프라 (CORBA, MOM) │
│ └── 2010s: Express.js 요청 처리 함수 체인 │
│ │
│ Interceptor (인터셉터) │
│ ├── 의미: 행위 개념 (중간에 삽입되어 가로채는 동작) │
│ ├── 기원: 1990년대 CORBA 분산시스템 │
│ └── 공식 문서화: POSA2 (2000) │
│ │
│ Filter (필터) │
│ ├── 의미: 처리 개념 (통과/걸러냄 판단) │
│ ├── 기원: Java Servlet 2.3 (2000-2001) │
│ └── 패턴 문서화: Core J2EE Patterns (2001) │
│ "Intercepting Filter" 패턴 │
│ │
│ 관계: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 동의어가 아니지만, 완전히 분리된 것도 아님 │ │
│ │ │ │
│ │ ┌─── Middleware (어디에?) ───────────────────┐ │ │
│ │ │ ┌─── Interceptor (무엇을?) ────────┐ │ │ │
│ │ │ │ ┌─── Filter (어떻게?) ────┐ │ │ │ │
│ │ │ │ │ 통과/차단 결정 │ │ │ │ │
│ │ │ │ └─────────────────────────┘ │ │ │ │
│ │ │ │ 가로채서 처리 │ │ │ │
│ │ │ └──────────────────────────────────┘ │ │ │
│ │ │ 중간 계층에 위치 │ │ │
│ │ └────────────────────────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ Chain of Responsibility (GoF)는 이 중 어느 것이든 │
│ 구현할 수 있는 구조적 도구 │
│ │
└─────────────────────────────────────────────────────────────────┘
2.3 학술적 출처
┌─────────────────────────────────────────────────────────────────┐
│ 핵심 문헌 │
│ │
│ 1. GoF "Design Patterns" (1994) │
│ ├── Gamma, Helm, Johnson, Vlissides │
│ ├── Chain of Responsibility 패턴 정의 │
│ └── "Avoid coupling the sender of a request to its │
│ receiver by giving more than one object a chance │
│ to handle the request." │
│ │
│ 2. POSA1 "Pattern-Oriented Software Architecture" (1996) │
│ └── Pipes and Filters 아키텍처 패턴 │
│ │
│ 3. POSA2 (2000년 9월) │
│ ├── Schmidt, Stal, Rohnert, Buschmann │
│ ├── Interceptor를 아키텍처 패턴으로 공식 문서화 │
│ └── "Interceptor allows services to be added │
│ transparently to a framework and triggered │
│ automatically when certain events occur." │
│ │
│ 4. Core J2EE Patterns (2001) │
│ └── "Intercepting Filter" 패턴 문서화 │
│ │
│ POSA2 Interceptor의 핵심 특성 4가지: │
│ ├── 투명성 (Transparency) │
│ ├── 런타임 동적 등록 │
│ ├── 이벤트 기반 트리거 │
│ └── 비침습적 (Non-invasive) │
│ │
└─────────────────────────────────────────────────────────────────┘
3. 개념이 나오게 된 배경: CGI의 문제
┌─────────────────────────────────────────────────────────────────┐
│ CGI 시대의 근본적 문제 (1993-1998) │
│ │
│ CGI란? │
│ ├── 1993년 NCSA의 Rob McCool이 명세 작성 │
│ ├── Common Gateway Interface │
│ └── 웹 서버가 외부 프로그램을 실행하여 동적 콘텐츠 생성 │
│ │
│ 동작 방식: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 브라우저 ──► 웹서버 ──► fork() ──► CGI 스크립트 실행 │ │
│ │ │ │ │
│ │ └── 요청마다 새 OS 프로세스 │ │
│ │ 생성 → 처리 → 종료 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 문제: 크로스커팅 관심사의 코드 중복 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ login.cgi → 인증 검사 코드 + 로깅 코드 + 비즈니스 │ │
│ │ order.cgi → 인증 검사 코드 + 로깅 코드 + 비즈니스 │ │
│ │ profile.cgi → 인증 검사 코드 + 로깅 코드 + 비즈니스 │ │
│ │ report.cgi → 인증 검사 코드 + 로깅 코드 + 비즈니스 │ │
│ │ ... │ │
│ │ (100개 스크립트에 인증 로직 100번 복사!) │ │
│ │ │ │
│ │ 인증 방식 변경되면? → 100개 파일 전부 수정 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 추가 문제: │
│ ├── 상태 비보존: 요청마다 프로세스가 죽으므로 상태 유지 불가 │
│ ├── 보안 취약성: 각 스크립트가 개별적으로 보안 처리 │
│ └── 성능: fork()는 비용이 매우 큰 시스템 콜 │
│ │
│ 1996년 FastCGI (Open Market): │
│ ├── 프로세스 재사용으로 성능 문제 해결 │
│ └── 그러나 코드 중복 문제는 여전! │
│ │
│ 이 코드 중복 문제를 해결하기 위해 │
│ Interceptor/Middleware/Filter 패턴이 등장 │
│ │
└─────────────────────────────────────────────────────────────────┘
4. 역사적 진화 타임라인
4.1 전체 타임라인
┌─────────────────────────────────────────────────────────────────┐
│ 진화 타임라인 │
│ │
│ 1993 ── CGI (NCSA, Rob McCool) │
│ │ 요청마다 프로세스 fork(), 코드 중복 만연 │
│ │ │
│ 1994 ── GoF Chain of Responsibility │
│ │ Gamma, Helm, Johnson, Vlissides │
│ │ 체인의 "하나"만 요청 처리 (순수 CoR) │
│ │ │
│ 1996 ── FastCGI (Open Market) │
│ │ 프로세스 재사용, 코드 중복은 미해결 │
│ │ POSA1 Pipes and Filters 아키텍처 패턴 │
│ │ │
│ 2000 ── POSA2 Interceptor 패턴 공식 문서화 │
│ │ Schmidt, Stal, Rohnert, Buschmann │
│ │ │
│ 2001 ── Java Servlet 2.3 Filter (JSR-053) │
│ │ Danny Coward 주도, web.xml 선언적 필터 체인 │
│ │ Core J2EE Patterns "Intercepting Filter" │
│ │ │
│ 2002 ── WebWork/XWork 인터셉터 │
│ │ Rickard Oberg, Patrick Lightbody (OpenSymphony) │
│ │ 인터셉터 스택 개념 │
│ │ │
│ 2003 ── Spring Framework 0.9 │
│ │ Rod Johnson, HandlerInterceptor │
│ │ Django 프로젝트 시작 (Adrian Holovaty, Simon Willison) │
│ │ │
│ 2004 ── Spring 1.0, Ruby on Rails 공개 (DHH) │
│ │ Rails before_filter, CoC 철학 │
│ │ │
│ 2005 ── Rails 1.0, Django 공개 │
│ │ │
│ 2006 ── Struts 2 (XWork 인터셉터 흡수) │
│ │ │
│ 2007 ── Rack (Christian Neukirchen) │
│ │ Ruby 서버-프레임워크 표준 인터페이스 │
│ │ Python WSGI (PEP 333, 2003)에서 영감 │
│ │ │
│ 2009 ── ASP.NET MVC 1.0 (Scott Guthrie) │
│ │ 속성(Attribute) 기반 Action Filter │
│ │ │
│ 2010 ── Express.js (TJ Holowaychuk) │
│ │ Connect + Express, next() 함수 체인 │
│ │ Sinatra에서 영감 │
│ │ │
│ 2013 ── Koa.js (TJ Holowaychuk) │
│ │ Generator → async/await 기반 │
│ │ 양파 모델(Onion Model) 도입 │
│ │ Rails 4: before_filter → before_action 이름 변경 │
│ │ │
│ 2014 ── Express 4.0 (Connect 의존성 분리) │
│ │ │
│ 2016 ── gRPC 인터셉터 (Google) │
│ │ 4가지 타입: Unary/Streaming x 서버/클라이언트 │
│ │ Django 1.10: 신형 함수형 양파 모델 │
│ │ │
│ 현재 ── 서비스 메시 (Envoy/Istio) │
│ 코드 수정 없이 인프라 레벨 인터셉션 │
│ │
└─────────────────────────────────────────────────────────────────┘
4.2 주요 전환점 상세
Java Servlet Filter (2001)
┌─────────────────────────────────────────────────────────────────┐
│ Servlet Filter - 표준화된 필터 체인 │
│ │
│ 배경: │
│ ├── JSR-053, Danny Coward 주도 │
│ ├── 2000년 10월 Proposed Final Draft │
│ └── Servlet 2.3 이전: "서블릿 체이닝" 비공식 메커니즘 (비표준) │
│ │
│ 핵심: web.xml에서 선언적으로 필터 체인 구성 │
│ │
│ <!-- web.xml --> │
│ <filter> │
│ <filter-name>AuthFilter</filter-name> │
│ <filter-class>com.example.AuthFilter</filter-class> │
│ </filter> │
│ <filter-mapping> │
│ <filter-name>AuthFilter</filter-name> │
│ <url-pattern>/*</url-pattern> │
│ </filter-mapping> │
│ │
│ 동작 흐름: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 요청 ──► AuthFilter ──► LogFilter ──► Servlet │ │
│ │ │ │
│ │ 각 Filter에서 FilterChain.doFilter() 호출으로 │ │
│ │ 체인 실행 또는 중단 결정 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 의의: 최초의 표준화된 웹 필터 메커니즘 │
│ │
└─────────────────────────────────────────────────────────────────┘
Spring MVC HandlerInterceptor (2003-2004)
┌─────────────────────────────────────────────────────────────────┐
│ Spring HandlerInterceptor vs Servlet Filter │
│ │
│ Rod Johnson "Expert One-on-One J2EE Design and Development" │
│ (2002.10) → Spring 0.9 (2003.6) → Spring 1.0 (2004.3) │
│ │
│ Servlet Filter와의 핵심 차이: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Servlet Filter: │ │
│ │ ├── 서블릿 컨테이너 레벨 (Tomcat 등) │ │
│ │ ├── Spring Bean에 접근 어려움 │ │
│ │ └── 모든 요청에 대해 동작 │ │
│ │ │ │
│ │ Spring HandlerInterceptor: │ │
│ │ ├── 프레임워크 레벨 (DispatcherServlet 내부) │ │
│ │ ├── Spring Bean 완전 접근 가능 │ │
│ │ ├── handler 객체로 컨트롤러 정보 접근 │ │
│ │ └── ModelAndView 조작 가능 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 실행 위치 비교: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 요청 ──► [Servlet Filter] ──► DispatcherServlet │ │
│ │ │ │ │
│ │ [HandlerInterceptor] │ │
│ │ │ │ │
│ │ Controller │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
Express.js 미들웨어 (2010)
┌─────────────────────────────────────────────────────────────────┐
│ Express.js - next()로 체인 제어 │
│ │
│ TJ Holowaychuk, Connect + Express, Sinatra에서 영감 │
│ │
│ // Express 미들웨어 기본 구조 │
│ app.use((req, res, next) => { │
│ console.log(`${req.method} ${req.url}`); │
│ next(); // 다음 미들웨어로 전달 │
│ }); │
│ │
│ app.use((req, res, next) => { │
│ if (!req.headers.authorization) { │
│ return res.status(401).send('Unauthorized'); │
│ // next() 미호출 → 체인 중단 │
│ } │
│ next(); │
│ }); │
│ │
│ 핵심 특징: │
│ ├── next() 함수로 체인 진행/중단을 제어 │
│ ├── 순서 = app.use() 호출 순서 │
│ ├── Express 4.0 (2014): Connect 의존성 완전 분리 │
│ └── JavaScript 생태계에서 "미들웨어"라는 용어를 대중화 │
│ │
└─────────────────────────────────────────────────────────────────┘
5. 핵심 디자인 패턴
5.1 GoF Chain of Responsibility (1994)
┌─────────────────────────────────────────────────────────────────┐
│ Chain of Responsibility (GoF 1994) │
│ │
│ Intent: │
│ "Avoid coupling the sender of a request to its receiver │
│ by giving more than one object a chance to handle │
│ the request." │
│ │
│ 원형 예시: GUI 컨텍스트 민감 도움말 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 사용자가 버튼 위에서 F1 누름 │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ Button ──(처리 못함)──► Panel ──(처리 못함)──► Dialog │ │
│ │ │ │ │
│ │ (도움말 표시!) │ │
│ │ │ │
│ │ 핵심: 체인의 "오직 하나"만이 요청을 처리 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ UML 구조: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Client ──► Handler (abstract) │ │
│ │ │ handleRequest() │ │
│ │ │ successor: Handler │ │
│ │ │ │ │
│ │ ┌─────┴──────┐ │ │
│ │ ▼ ▼ │ │
│ │ ConcreteHandler1 ConcreteHandler2 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 적용 시점: │
│ ├── 처리 객체가 미리 알려져 있지 않을 때 │
│ ├── 여러 객체 중 하나가 처리해야 할 때 │
│ └── 처리 객체를 동적으로 변경하고 싶을 때 │
│ │
│ 결과: │
│ ├── 장점: 결합도 감소, 유연성 증가 │
│ └── 단점: 처리 보장 없음 (체인 끝까지 아무도 안 처리할 수도) │
│ │
└─────────────────────────────────────────────────────────────────┘
5.2 Pipes and Filters (POSA Vol.1, 1996)
┌─────────────────────────────────────────────────────────────────┐
│ Pipes and Filters 아키텍처 패턴 │
│ │
│ 구조: │
│ Data Source ──► Filter ──pipe──► Filter ──pipe──► Data Sink │
│ │
│ 가장 유명한 구현: Unix 파이프 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ $ cat access.log | grep "ERROR" | sort | uniq -c │ │
│ │ │ │
│ │ cat ──► grep ──► sort ──► uniq │ │
│ │ (소스) (필터) (필터) (싱크) │ │
│ │ │ │
│ │ 모든 필터가 데이터를 처리하고 다음으로 전달 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ CoR과의 핵심 차이: │
│ ┌────────────────┬──────────────────┬────────────────────┐ │
│ │ 구분 │ Chain of Resp. │ Pipes and Filters │ │
│ ├────────────────┼──────────────────┼────────────────────┤ │
│ │ 처리 │ 하나가 처리 후 │ 모든 필터가 │ │
│ │ │ 체인 중단 │ 데이터 처리 │ │
│ ├────────────────┼──────────────────┼────────────────────┤ │
│ │ 패턴 수준 │ 객체 디자인 패턴 │ 아키텍처 패턴 │ │
│ ├────────────────┼──────────────────┼────────────────────┤ │
│ │ 데이터 변환 │ 변환 없이 전달 │ 각 단계에서 변환 │ │
│ └────────────────┴──────────────────┴────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
5.3 POSA2 Interceptor 패턴 (2000)
┌─────────────────────────────────────────────────────────────────┐
│ POSA2 Interceptor 패턴 │
│ │
│ Douglas C. Schmidt의 ACE/TAO 프레임워크에서 발전 │
│ CORBA "Portable Interceptor" 표준에서 체계적 등장 │
│ │
│ 정의: │
│ "Interceptor allows services to be added transparently │
│ to a framework and triggered automatically when certain │
│ events occur." │
│ │
│ GoF CoR과의 핵심 차이: │
│ ┌────────────────┬──────────────────┬────────────────────┐ │
│ │ 구분 │ CoR (GoF 1994) │ Interceptor │ │
│ │ │ │ (POSA2 2000) │ │
│ ├────────────────┼──────────────────┼────────────────────┤ │
│ │ 처리 주체 │ 체인 중 하나만 │ 모든 인터셉터가 │ │
│ │ │ 처리 │ 실행 보장 │ │
│ ├────────────────┼──────────────────┼────────────────────┤ │
│ │ 등록 시점 │ 주로 컴파일 타임 │ 런타임 동적 등록 │ │
│ ├────────────────┼──────────────────┼────────────────────┤ │
│ │ 대상 │ 범용 요청 라우팅 │ 프레임워크 확장 │ │
│ ├────────────────┼──────────────────┼────────────────────┤ │
│ │ 투명성 │ 명시적 체인 구성 │ 나머지 시스템에 │ │
│ │ │ │ 투명 │ │
│ └────────────────┴──────────────────┴────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
5.4 패턴 계보도
┌─────────────────────────────────────────────────────────────────┐
│ 패턴 계보도 │
│ │
│ GoF Chain of Responsibility (1994) │
│ │ 순수 CoR: 하나만 처리, 나머지 무시 │
│ │ │
│ ├── Pipeline 변형 (모두 실행) │
│ │ │ │
│ │ ├── POSA1 Pipes and Filters (1996) │
│ │ │ 아키텍처 패턴, Unix 파이프 │
│ │ │ │
│ │ ├── POSA2 Interceptor (2000) │
│ │ │ 프레임워크 확장, 투명성/동적 등록 │
│ │ │ │
│ │ └── Middleware Pattern (2010~) │
│ │ Express.js가 대중화한 웹 프레임워크 패턴 │
│ │ │
│ └── 현대적 확장 │
│ │ │
│ ├── 리액티브: Spring WebFlux WebFilter │
│ │ Mono<Void> 기반 논블로킹 체인 │
│ │ │
│ └── 인프라: Envoy/Istio 사이드카 │
│ 코드 수정 없이 인프라 레벨 인터셉션 │
│ │
└─────────────────────────────────────────────────────────────────┘
6. 작동 원리
6.1 기본 구조: 체인 실행
┌─────────────────────────────────────────────────────────────────┐
│ 인터셉터/미들웨어 기본 동작 │
│ │
│ 선형 모델 (Express.js 스타일): │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 요청 ──► [MW1] ──► [MW2] ──► [MW3] ──► 핸들러 │ │
│ │ │ │ │ │ │
│ │ 로깅 인증 압축 │ │
│ │ │ │
│ │ 각 미들웨어에서 next() 호출 → 다음으로 전달 │ │
│ │ next() 미호출 → 체인 중단 (예: 인증 실패 시) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 양파 모델 (Koa.js 스타일): │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌───────────────────────────────┐ │ │
│ │ │ MW1 (before) │ │ │
│ │ │ ┌───────────────────────┐ │ │ │
│ │ │ │ MW2 (before) │ │ │ │
│ │ │ │ ┌───────────────┐ │ │ │ │
│ │ 요청 ──►│ │ │ 핸들러 │ │ │──► 응답 │ │
│ │ │ │ └───────────────┘ │ │ │ │
│ │ │ │ MW2 (after) │ │ │ │
│ │ │ └───────────────────────┘ │ │ │
│ │ │ MW1 (after) │ │ │
│ │ └───────────────────────────────┘ │ │
│ │ │ │
│ │ await next() 전 = preHandle (요청 가공) │ │
│ │ await next() 후 = postHandle (응답 가공) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
6.2 Koa.js 양파 모델 코드 예시
// Koa.js 양파 모델 - 요청과 응답이 동일 계층을 통과
app.use(async (ctx, next) => {
const start = Date.now(); // ① 요청 들어올 때
await next(); // ② 다음 미들웨어로
const ms = Date.now() - start; // ⑤ 응답 나올 때
ctx.set('X-Response-Time', `${ms}ms`);
});
app.use(async (ctx, next) => {
console.log('요청 시작'); // ③ 요청 들어올 때
await next(); // → 핸들러 실행
console.log('응답 완료'); // ④ 응답 나올 때
});
// 실행 순서: ① → ② → ③ → 핸들러 → ④ → ⑤
┌─────────────────────────────────────────────────────────────────┐
│ koa-compose 내부 구현 원리 │
│ │
│ function compose(middleware) { │
│ return function(context, next) { │
│ let index = -1; │
│ return dispatch(0); │
│ │
│ function dispatch(i) { │
│ index = i; │
│ let fn = middleware[i]; │
│ if (i === middleware.length) fn = next; │
│ if (!fn) return Promise.resolve(); │
│ return fn(context, () => dispatch(i + 1)); │
│ } │
│ }; │
│ } │
│ │
│ 핵심: 재귀적 dispatch 함수가 양파 모델을 구현 │
│ next()를 호출하면 dispatch(i+1)이 실행되어 다음 계층으로 │
│ await로 기다렸다가 돌아오면 나머지 코드(after) 실행 │
│ │
└─────────────────────────────────────────────────────────────────┘
6.3 Spring HandlerInterceptor 생명주기
// Spring HandlerInterceptor 3단계 생명주기
public class LoggingInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
// 컨트롤러 실행 전
// false 반환 시 체인 중단
log.info("요청: {} {}", request.getMethod(), request.getRequestURI());
return true;
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) {
// 컨트롤러 실행 후, 뷰 렌더링 전
// ModelAndView 조작 가능
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
// 뷰 렌더링 완료 후 (리소스 정리)
}
}
7. 장점
┌─────────────────────────────────────────────────────────────────┐
│ Interceptor/Middleware의 7가지 장점 │
│ │
│ 1. 관심사의 분리 (Separation of Concerns) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Before: 컨트롤러 안에 인증+로깅+압축+비즈니스 혼재 │ │
│ │ After: 인프라 관심사를 비즈니스 로직에서 완전 격리 │ │
│ │ │ │
│ │ 컨트롤러는 비즈니스 로직에만 집중 │ │
│ │ 인증/로깅/압축은 미들웨어가 처리 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 2. DRY 원칙 (Don't Repeat Yourself) │
│ ├── 100개 엔드포인트에 JWT 검증 코드를 100번 쓸 필요 없음 │
│ └── 미들웨어 하나로 전체 적용 │
│ │
│ 3. 개방-폐쇄 원칙 (Open-Closed Principle) │
│ ├── 기존 코드를 변경하지 않고 │
│ └── 새로운 인터셉터를 체인에 추가 │
│ │
│ 4. 조합 가능성 (Composability) │
│ ├── 모노이드 구조: 인터셉터 + 인터셉터 = 인터셉터 │
│ └── 순서에 따라 자유롭게 조합 │
│ │
│ 5. 테스트 가능성 (Testability) │
│ ├── 각 인터셉터를 독립적으로 단위 테스트 가능 │
│ └── Mock 요청/응답으로 격리된 테스트 │
│ │
│ 6. 단일 책임 원칙 (Single Responsibility) │
│ ├── 인증 미들웨어: 인증만 │
│ ├── 로깅 미들웨어: 로깅만 │
│ └── 압축 미들웨어: 압축만 │
│ │
│ 7. 투명성/비침투성 (Transparency) │
│ ├── 비즈니스 로직은 인터셉터 존재를 모름 │
│ └── 나머지 시스템에 영향 없이 추가/제거 │
│ │
└─────────────────────────────────────────────────────────────────┘
8. 단점과 한계
┌─────────────────────────────────────────────────────────────────┐
│ 6가지 단점과 한계 │
│ │
│ 1. 비가시적 제어 흐름 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ "내 요청이 어디서 막혔지?" │ │
│ │ │ │
│ │ 요청 ──► [?] ──► [?] ──► [?] ──► [?] ──► 핸들러 │ │
│ │ 어디서 401이 나오는 거지...? │ │
│ │ │ │
│ │ Ben Nadel: │ │
│ │ "HTTP 인터셉터는 본질적으로 공유된 전역 상태" │ │
│ │ │ │
│ │ Richard Marmorstein "Beware Middleware": │ │
│ │ "정적 분석 도구가 거의 도움이 안 된다" │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 2. 순서 의존성 버그 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 올바른 순서: │ │
│ │ Authentication ──► Authorization ──► RateLimit │ │
│ │ (누구인지 확인) (권한 확인) (요청 제한) │ │
│ │ │ │
│ │ 잘못된 순서: │ │
│ │ Authorization ──► Authentication ──► RateLimit │ │
│ │ (권한 확인?) (누군지도 모르는데?) │ │
│ │ → 모든 요청 401! 에러 없이 조용히 잘못된 동작! │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 3. 성능 오버헤드 ("미들웨어 세금") │
│ ├── 모든 요청이 전체 체인을 통과 │
│ ├── 각 미들웨어의 실행 시간이 누적 │
│ └── Vercel 사례: 요청당 60-70ms 추가 지연 발생 │
│ │
│ 4. 전역 vs 로컬 스코프 문제 │
│ ├── 전역 미들웨어가 불필요한 경로에도 적용 │
│ ├── /health 체크에도 인증 미들웨어 실행 │
│ └── 예외 경로 관리가 복잡해짐 │
│ │
│ 5. 숨겨진 결합 (Hidden Coupling) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ // RateLimit 미들웨어 │ │
│ │ if (req.isAdmin) { // 어디서 설정한 거지? │ │
│ │ limit = 1000; // Auth 미들웨어에서 설정했나? │ │
│ │ } // 의존 관계가 코드에 명시 안 됨! │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 6. 체인 전체 테스트의 복잡성 │
│ ├── 개별 미들웨어 단위 테스트는 쉬움 │
│ ├── 체인 전체의 상호작용 테스트는 통합 테스트 필요 │
│ └── 순서 의존성까지 검증하려면 테스트가 복잡해짐 │
│ │
└─────────────────────────────────────────────────────────────────┘
9. 안티패턴
┌─────────────────────────────────────────────────────────────────┐
│ 5가지 안티패턴 │
│ │
│ 1. 전지전능 인터셉터 (God Interceptor) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ // 하나의 미들웨어가 모든 것을 처리 │ │
│ │ app.use((req, res, next) => { │ │
│ │ // 인증 검사 │ │
│ │ // 권한 검사 │ │
│ │ // 로깅 │ │
│ │ // 입력 검증 │ │
│ │ // CORS 처리 │ │
│ │ // 레이트 리밋 │ │
│ │ // 캐싱 │ │
│ │ // ... 500줄 │ │
│ │ next(); │ │
│ │ }); │ │
│ │ │ │
│ │ → 단일 책임 원칙 위반, 테스트/유지보수 불가능 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 2. 인터셉터 수프 (Interceptor Soup) │
│ ├── 미들웨어가 30개, 40개 쌓여서 흐름 파악 불가 │
│ ├── 어떤 미들웨어가 어떤 영향을 주는지 추적 어려움 │
│ └── 디버깅 시 "수프 속에서 바늘 찾기" │
│ │
│ 3. 비즈니스 로직의 인터셉터화 │
│ ├── 횡단 관심사만 미들웨어에 적합 │
│ ├── 비즈니스 규칙을 미들웨어에 넣으면 로직이 숨겨짐 │
│ └── "왜 이 주문이 거부됐지?" → 미들웨어 20개를 뒤져야 │
│ │
│ 4. 상태 저장 인터셉터 (Stateful Interceptor) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ let requestCount = 0; // 인터셉터에 상태 보관 │ │
│ │ │ │
│ │ app.use((req, res, next) => { │ │
│ │ requestCount++; // 동시 요청에서 레이스 컨디션! │ │
│ │ next(); │ │
│ │ }); │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 5. 필터 간 정보 과다 공유 │
│ ├── req 객체에 임의 속성을 과도하게 부착 │
│ ├── req.user, req.isAdmin, req.tenant, req.featureFlags... │
│ └── 미들웨어 간 암묵적 계약이 되어 결합도 상승 │
│ │
└─────────────────────────────────────────────────────────────────┘
10. 대안 비교
┌─────────────────────────────────────────────────────────────────┐
│ Interceptor/Middleware의 4가지 대안 │
│ │
│ 1. AOP (Aspect-Oriented Programming) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 특징: │ │
│ │ ├── 임의 메서드에 정밀 적용 (HTTP 경계 밖에서도) │ │
│ │ ├── 타입 안전 (컴파일 타임 위빙) │ │
│ │ └── 포인트컷으로 적용 대상 세밀 지정 │ │
│ │ │ │
│ │ 적합한 경우: │ │
│ │ ├── HTTP 요청/응답이 아닌 일반 메서드에 적용할 때 │ │
│ │ ├── 트랜잭션 관리, 캐싱, 메서드 레벨 보안 │ │
│ │ └── Spring @Transactional이 대표적 AOP 활용 │ │
│ │ │ │
│ │ vs Interceptor: │ │
│ │ 인터셉터는 HTTP 경계에서 동작, AOP는 어디서든 동작 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 2. 데코레이터 패턴 (Decorator Pattern) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 특징: │ │
│ │ ├── 특정 객체 인스턴스를 래핑 │ │
│ │ ├── 코드에서 명확히 보임 (명시적) │ │
│ │ └── 체인이 아닌 중첩 구조 │ │
│ │ │ │
│ │ // 데코레이터: 누가 래핑했는지 코드에서 보임 │ │
│ │ service = new LoggingDecorator( │ │
│ │ new CachingDecorator( │ │
│ │ new RealService())); │ │
│ │ │ │
│ │ vs Interceptor: │ │
│ │ 데코레이터는 명시적, 인터셉터는 투명 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 3. 이벤트 드리븐 / 옵저버 패턴 │
│ ├── 비동기 브로드캐스트, 느슨한 결합 │
│ ├── 요청/응답 체인이 아닌 발행/구독 모델 │
│ └── 적합: 알림, 감사 로그 등 비동기 처리 │
│ │
│ 4. 직접 합성 (함수형 접근) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ // 명시적 매개변수와 반환값 │ │
│ │ const result = compress( │ │
│ │ authorize( │ │
│ │ authenticate(request))); │ │
│ │ │ │
│ │ 장점: 타입 안전, 흐름이 코드에서 명확히 보임 │ │
│ │ 단점: 규모가 커지면 관리 어려움 │ │
│ │ 적합: 소규모 앱, 단순한 파이프라인 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 요약 비교: │
│ ┌───────────┬──────────────┬────────────┬──────────────┐ │
│ │ 대안 │ 투명성 │ 적용 범위 │ 타입 안전성 │ │
│ ├───────────┼──────────────┼────────────┼──────────────┤ │
│ │ AOP │ 높음 │ 임의 메서드│ 높음 │ │
│ │ Decorator │ 낮음(명시적) │ 특정 객체 │ 높음 │ │
│ │ Observer │ 높음 │ 이벤트 │ 중간 │ │
│ │ 함수 합성│ 낮음(명시적) │ 전체 │ 매우 높음 │ │
│ │ 미들웨어 │ 높음 │ HTTP 경계 │ 낮음 │ │
│ └───────────┴──────────────┴────────────┴──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
11. 현대적 변형
11.1 Koa.js 양파 모델 (2013)
┌─────────────────────────────────────────────────────────────────┐
│ Koa.js 양파 모델 │
│ │
│ TJ Holowaychuk, Generator → async/await 기반 │
│ │
│ Express와의 차이: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Express (선형): │ │
│ │ 요청 ──► MW1 ──► MW2 ──► MW3 ──► 핸들러 ──► 응답 │ │
│ │ (요청 방향으로만 진행) │ │
│ │ │ │
│ │ Koa (양파): │ │
│ │ 요청 ──► MW1.전 ──► MW2.전 ──► 핸들러 │ │
│ │ 응답 ◄── MW1.후 ◄── MW2.후 ◄──┘ │ │
│ │ (요청/응답이 동일 계층을 왕복 통과) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 이점: │
│ ├── 한 미들웨어 안에서 요청 전/후 처리를 모두 작성 │
│ ├── 응답 시간 측정이 자연스러움 (start → next → end) │
│ └── 에러 처리가 try/catch로 직관적 │
│ │
└─────────────────────────────────────────────────────────────────┘
11.2 Spring WebFlux WebFilter (리액티브)
┌─────────────────────────────────────────────────────────────────┐
│ Spring WebFlux WebFilter │
│ │
│ Mono<Void> 기반 논블로킹 필터 체인 │
│ │
│ @Component │
│ public class LogFilter implements WebFilter { │
│ @Override │
│ public Mono<Void> filter(ServerWebExchange exchange, │
│ WebFilterChain chain) { │
│ log.info("요청: {}", exchange.getRequest().getPath()); │
│ return chain.filter(exchange) │
│ .doOnSuccess(v -> log.info("완료")); │
│ } │
│ } │
│ │
│ 기존 Servlet Filter와의 차이: │
│ ├── 블로킹 없이 비동기 체인 실행 │
│ ├── Reactor 스트림 안에서 자연스럽게 조합 │
│ └── 논블로킹 I/O와 완벽 호환 │
│ │
└─────────────────────────────────────────────────────────────────┘
11.3 gRPC 인터셉터 (2016)
┌─────────────────────────────────────────────────────────────────┐
│ gRPC 인터셉터 │
│ │
│ Google 공개, HTTP가 아닌 RPC 프로토콜에서의 인터셉션 │
│ │
│ 4가지 타입: │
│ ┌────────────────┬──────────────────┬────────────────────┐ │
│ │ │ 서버 (Server) │ 클라이언트 (Client)│ │
│ ├────────────────┼──────────────────┼────────────────────┤ │
│ │ Unary │ UnaryServer │ UnaryClient │ │
│ │ (단일 요청) │ Interceptor │ Interceptor │ │
│ ├────────────────┼──────────────────┼────────────────────┤ │
│ │ Streaming │ StreamServer │ StreamClient │ │
│ │ (스트리밍) │ Interceptor │ Interceptor │ │
│ └────────────────┴──────────────────┴────────────────────┘ │
│ │
│ 특징: │
│ ├── 양방향 스트리밍 지원 │
│ ├── 메타데이터(HTTP 헤더 대응) 조작 │
│ └── 서버/클라이언트 양쪽 모두에서 인터셉션 가능 │
│ │
└─────────────────────────────────────────────────────────────────┘
11.4 서비스 메시 사이드카 (Envoy/Istio)
┌─────────────────────────────────────────────────────────────────┐
│ 서비스 메시: 인프라 레벨 인터셉션 │
│ │
│ POSA2 Interceptor의 궁극적 진화 │
│ "코드 수정 없이" 인프라 레벨에서 모든 것을 가로채기 │
│ │
│ 동작 방식: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 서비스 A 서비스 B │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ App │──│ Envoy │─────│ Envoy │──│ App │ │ │
│ │ │ │ │ (Sidecar)│ │ (Sidecar)│ │ │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └─────┘ │ │
│ │ │ │
│ │ iptables로 모든 트래픽을 Envoy 프록시로 리다이렉트 │ │
│ │ 앱은 Envoy의 존재를 모름 (완벽한 투명성) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 제공 기능 (코드 변경 없이): │
│ ├── mTLS (상호 TLS 인증) │
│ ├── 로드밸런싱 │
│ ├── 서킷브레이커 │
│ ├── 재시도 (Retry) │
│ ├── 분산 트레이싱 │
│ └── 트래픽 관찰 (Observability) │
│ │
│ 의의: │
│ 인터셉터 패턴이 코드 레벨 → 프레임워크 레벨 → 인프라 레벨로 │
│ 진화한 최종 형태 │
│ │
└─────────────────────────────────────────────────────────────────┘
12. 실무 교훈과 장애 사례
┌─────────────────────────────────────────────────────────────────┐
│ 실제 장애 사례와 교훈 │
│ │
│ 사례 1: "4시간짜리 한 줄 수정" │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 증상: 특정 API에서 간헐적으로 403 Forbidden 반환 │ │
│ │ 원인: 미들웨어 순서가 뒤바뀌어 있었음 │ │
│ │ Authorization이 Authentication보다 먼저 실행 │ │
│ │ 수정: 미들웨어 등록 순서 한 줄 변경 │ │
│ │ 소요: 원인 파악에 4시간 │ │
│ │ │ │
│ │ 교훈: 순서 의존성 버그는 에러 없이 조용히 발생하므로 │ │
│ │ 디버깅이 매우 어렵다 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 사례 2: Angular "부족 지식 문제" │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 증상: 새로 합류한 팀원이 HTTP 인터셉터 동작을 │ │
│ │ 이해하지 못해 같은 로직을 서비스에 중복 구현 │ │
│ │ 원인: 인터셉터는 코드에서 명시적으로 보이지 않아 │ │
│ │ 존재 자체를 파악하기 어려움 │ │
│ │ │ │
│ │ 교훈: 인터셉터의 투명성은 장점이자 단점 │ │
│ │ 팀 전체가 아키텍처를 공유해야 함 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 사례 3: ASP.NET 스트림 재진입 문제 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 증상: 미들웨어에서 Request Body를 읽은 후 │ │
│ │ 다음 미들웨어/핸들러에서 빈 본문 │ │
│ │ 원인: HTTP 요청 스트림은 한 번만 읽을 수 있음 │ │
│ │ (Stream이 소진되면 되감기 필요) │ │
│ │ 수정: EnableBuffering()으로 스트림 재읽기 허용 │ │
│ │ │ │
│ │ 교훈: 미들웨어 간 공유 자원(스트림 등) 관리에 주의 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 사례 4: Vercel "60-70ms 미들웨어 세금" │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 증상: 모든 요청에 60-70ms 지연 추가 │ │
│ │ 원인: Edge Middleware가 모든 요청을 거침 │ │
│ │ 영향: 사용자 체감 성능 저하 │ │
│ │ │ │
│ │ 교훈: 미들웨어는 "무료"가 아님 │ │
│ │ 적용 범위를 신중하게 제한해야 함 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 사례 5: NestJS 생명주기 복잡성 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ NestJS 요청 처리 순서: │ │
│ │ Middleware → Guard → Interceptor(전) → Pipe → │ │
│ │ Handler → Interceptor(후) → ExceptionFilter │ │
│ │ │ │
│ │ 증상: 각 계층의 역할과 실행 순서 혼동으로 │ │
│ │ 로직이 잘못된 계층에 배치 │ │
│ │ │ │
│ │ 교훈: 프레임워크의 생명주기를 정확히 이해하고 │ │
│ │ 각 계층에 적합한 책임만 부여해야 함 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
13. 의사결정 가이드
┌─────────────────────────────────────────────────────────────────┐
│ "Interceptor/Middleware를 사용해야 할까?" │
│ │
│ Step 1: 횡단 관심사인가? │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 횡단 관심사 (미들웨어 적합): │ │
│ │ ├── 인증/인가 (Authentication/Authorization) │ │
│ │ ├── 요청/응답 로깅 │ │
│ │ ├── CORS 처리 │ │
│ │ ├── 압축 (gzip) │ │
│ │ ├── 레이트 리밋 │ │
│ │ ├── 요청 ID 부여 (트레이싱) │ │
│ │ └── 에러 핸들링 │ │
│ │ │ │
│ │ 비즈니스 로직 (미들웨어 부적합): │ │
│ │ ├── 주문 검증 규칙 │ │
│ │ ├── 할인 계산 │ │
│ │ ├── 재고 확인 │ │
│ │ └── 비즈니스 이벤트 처리 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ Step 2: 적용 범위는? │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 전체 엔드포인트 → 전역 미들웨어 │ │
│ │ 특정 경로 그룹 → 라우트 레벨 미들웨어 │ │
│ │ 특정 메서드 → AOP 또는 데코레이터 고려 │ │
│ │ HTTP 밖에서도 → AOP 선택 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ Step 3: 어떤 메커니즘을 선택할까? │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Spring 생태계: │ │
│ │ ├── 인코딩/보안 등 저수준 → Servlet Filter │ │
│ │ ├── 컨트롤러 전후 처리 → HandlerInterceptor │ │
│ │ ├── 메서드 레벨 관심사 → AOP (@Aspect) │ │
│ │ └── 리액티브 스택 → WebFilter │ │
│ │ │ │
│ │ Node.js 생태계: │ │
│ │ ├── 전역 관심사 → app.use() 미들웨어 │ │
│ │ ├── 라우트별 관심사 → 라우트 미들웨어 │ │
│ │ └── 응답 가공 필요 시 → Koa 양파 모델 │ │
│ │ │ │
│ │ 마이크로서비스: │ │
│ │ ├── 서비스 간 공통 관심사 → 서비스 메시 (Istio/Envoy) │ │
│ │ └── gRPC 통신 → gRPC 인터셉터 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 체크리스트: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 미들웨어 도입 전 확인: │ │
│ │ [ ] 횡단 관심사인가? (비즈니스 로직이 아닌가?) │ │
│ │ [ ] 순서 의존성이 문서화되어 있는가? │ │
│ │ [ ] 성능 영향을 측정했는가? │ │
│ │ [ ] 전역/로컬 스코프를 적절히 설정했는가? │ │
│ │ [ ] 미들웨어 간 숨겨진 결합은 없는가? │ │
│ │ [ ] 팀 전체가 미들웨어 체인을 이해하고 있는가? │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
14. 참고 자료
┌─────────────────────────────────────────────────────────────────┐
│ 참고 자료 │
│ │
│ 학술 문헌: │
│ ├── Gamma, Helm, Johnson, Vlissides │
│ │ "Design Patterns: Elements of Reusable Object-Oriented │
│ │ Software" (1994) - Chain of Responsibility │
│ │ │
│ ├── Buschmann, Meunier, Rohnert, Sommerlad, Stal │
│ │ "Pattern-Oriented Software Architecture Vol.1" (1996) │
│ │ - Pipes and Filters │
│ │ │
│ ├── Schmidt, Stal, Rohnert, Buschmann │
│ │ "Pattern-Oriented Software Architecture Vol.2" (2000) │
│ │ - Interceptor Pattern │
│ │ │
│ ├── Alur, Crupi, Malks │
│ │ "Core J2EE Patterns" (2001) - Intercepting Filter │
│ │ │
│ └── Rod Johnson │
│ "Expert One-on-One J2EE Design and Development" (2002) │
│ │
│ 표준 명세: │
│ ├── JSR-053: Java Servlet 2.3 (Danny Coward, 2000-2001) │
│ ├── PEP 333: Python WSGI (2003) │
│ └── CORBA Portable Interceptor Specification │
│ │
│ 프레임워크 역사: │
│ ├── Express.js - TJ Holowaychuk (2010) │
│ ├── Koa.js - TJ Holowaychuk (2013) │
│ ├── Ruby on Rails - DHH (2004) │
│ ├── Django - Adrian Holovaty, Simon Willison (2003/2005) │
│ ├── Spring Framework - Rod Johnson (2003/2004) │
│ ├── ASP.NET MVC - Scott Guthrie (2007/2009) │
│ ├── Rack - Christian Neukirchen (2007) │
│ ├── WebWork/XWork - Rickard Oberg, Patrick Lightbody (2002) │
│ └── gRPC - Google (2016) │
│ │
│ 비평 및 분석: │
│ ├── Ben Nadel - HTTP 인터셉터의 전역 상태 문제 분석 │
│ ├── Richard Marmorstein - "Beware Middleware" │
│ └── Vercel - Edge Middleware 성능 이슈 사례 │
│ │
│ 어원 자료: │
│ ├── intercipere (라틴어) - inter- + capere │
│ ├── Proto-Indo-European *kap- "붙잡다" │
│ ├── 영어 interceptor 최초 기록: 1590년대 │
│ └── "Middleware" 최초 사용: 1968 NATO Software Engineering │
│ Conference (Alex d'Agapeyeff) │
│ │
└─────────────────────────────────────────────────────────────────┘