Chain of Responsibility: 요청을 핸들러 체인에 순서대로 흘려보내기
요청을 순차 핸들러로 전달해 조건에 맞는 곳에서 처리하는 Chain of Responsibility 패턴의 구성 요소와 Java 구현을 정리한다.
2026-08-13 · 최초 발행 2025-10-14
정의와 동작 방식
Chain of Responsibility는 여러 핸들러(Handler)를 체인으로 엮어 요청을 순차 전달하고, 조건을 만족하는 핸들러에서 처리하는 행동 패턴(Behavioral Pattern)이다. 송신자(클라이언트)와 수신자(구체 핸들러)를 분리해 두기 때문에 처리 로직이 바뀌어도 클라이언트 코드는 영향을 받지 않는다.
체인 안에서 요청이 어떻게 흘러갈지는 정책으로 정한다 — 처리 후 종료할지, 일부만 처리하고 다음으로 넘길지, 처리할 수 없으면 다음으로 위임할지, 실패하면 예외를 던지거나 대체 경로로 갈지를 핸들러마다 선택할 수 있다.
Handler 인터페이스와 공통 베이스
handle(request) 시그니처를 정의하고, 처리 여부를 반환하거나 다음으로 위임하는 규약을 정한다. next 연결은 공통 베이스 클래스에서 관리한다. 처리 규칙을 단일 책임으로 나눠 두면 테스트하기 쉽고 재사용하기도 쉽다.
체인 구성과 순서 제어
핸들러를 등록하는 순서 자체가 의미를 가진다. 선제 필터링 → 인증/인가 → 변환 → 비즈니스 처리 → 감사 로깅 순으로 단계를 쌓는 식이다. 코드로 직접 연결하거나, DI 컨테이너, 설정 파일 기반 동적 구성으로 체인을 만든다.
핸들러를 추가하거나 빼는 방식으로 정책을 넣고 빼면 수정 범위가 좁아져 변경 리드타임을 20~40% 줄일 수 있다는 참고치도 있고, 공통 로깅·검증을 재사용하면 중복 코드를 30% 이상 줄일 수 있다(참고 수준 수치, 팀·도메인에 따라 달라질 수 있다).
전파 정책과 종료 조건
처리에 성공하면 체인을 끝내고, 처리할 수 없으면 다음으로 넘기고, 일부만 처리한 뒤 계속 전파하는 전략을 고를 수 있다. 실패는 즉시 중단(FAIL-FAST), 보상/대체 핸들러, 집계 응답 생성 중에서 선택한다.
오류·시간 예산·관찰 가능성
타임아웃, 재시도, 서킷 브레이커를 연계하면 안정성이 올라간다. 로깅·트레이싱·메트릭을 끼워 넣으면 구간별 지연과 실패율을 관찰할 수 있다.
동기/비동기, 프로세스 내/간 선택
프로세스 내 체인은 지연이 낮고 함수 호출 중심으로 간단하게 구성된다. 메시지/이벤트 기반 체인은 고가용성과 확장성이 좋지만 최종 일관성을 전제로 한다.
체인이 동작하는 순서
입력 → 처리 → 출력 단계에서 각 단계의 조건 분기와 실패 처리 흐름을 그대로 보여준다.
실무에서 어떻게 쓰이는가
웹/서버 미들웨어 체인
HTTP 요청 필터/인터셉터 체인을 예로 들면, 입력 정규화 → CORS → 인증 → 인가 → 속도 제한 → 로깅 순으로 적용한다. 처리할 수 없으면 즉시 반환하고, 성공하면 다음 단계로 위임한다.
데이터 검증·정제 파이프라인
필드 유효성, 도메인 규칙, 상호 참조 검증을 독립된 핸들러로 나눠 구성한다. 실패 사유를 모아서 보여줄지, 첫 실패에서 바로 중단할지는 전략으로 정한다.
보안 정책 적용
OTP, 디바이스 신뢰, IP 평판 같은 다중 인증 요소를 단계적으로 적용한다. 인가 스코프와 속성 기반 접근 제어(ABAC)도 체인으로 구성한다.
API 게이트웨이/메시지 처리
API 게이트웨이의 필터 체인은 라우팅, 캐싱, QoS, 서킷 브레이킹을 맡는다. 이벤트 스트림 처리라면 중복 제거 → 스키마 검증 → 정책 점검 → 싱크 전송 순서로 흐른다.
운영 업무·에스컬레이션
고객 문의나 알람은 우선순위와 전문성 기준으로 계층적으로 에스컬레이션한다. SLA 시간을 넘기면 다음 담당자나 자동화 처리로 넘긴다.
Java로 핸들러 체인 짜보기
전제조건: Java 17+, 표준 JDK 빌드 가능 환경
// Handler 인터페이스
interface Handler {
boolean handle(Request ctx); // true: 체인 계속, false: 체인 종료
Handler setNext(Handler next);
}
// 공통 베이스
abstract class BaseHandler implements Handler {
private Handler next;
public Handler setNext(Handler next) { this.next = next; return next; }
protected boolean next(Request ctx) { return next == null || next.handle(ctx); }
}
// 요청 컨텍스트
class Request {
String token;
String role;
String payload;
Request(String token, String role, String payload){ this.token=token; this.role=role; this.payload=payload; }
}
// 구체 핸들러들
class ValidationHandler extends BaseHandler {
public boolean handle(Request ctx) {
if (ctx.payload == null || ctx.payload.isBlank()) {
System.out.println("400 Bad Request");
return false; // 종료
}
return next(ctx);
}
}
class AuthHandler extends BaseHandler {
public boolean handle(Request ctx) {
if (ctx.token == null || !ctx.token.startsWith("Bearer ")) {
System.out.println("401 Unauthorized");
return false;
}
return next(ctx);
}
}
class AuthorizationHandler extends BaseHandler {
public boolean handle(Request ctx) {
if (!"ADMIN".equals(ctx.role)) {
System.out.println("403 Forbidden");
return false;
}
return next(ctx);
}
}
class BusinessHandler extends BaseHandler {
public boolean handle(Request ctx) {
// 비즈니스 처리
System.out.println("200 OK: processed " + ctx.payload);
return false; // 처리 후 종료
}
}
public class Main {
public static void main(String[] args) {
Handler chain = new ValidationHandler();
chain.setNext(new AuthHandler())
.setNext(new AuthorizationHandler())
.setNext(new BusinessHandler());
chain.handle(new Request("Bearer abc", "ADMIN", "work")); // 200 OK
chain.handle(new Request(null, "ADMIN", "work")); // 401
chain.handle(new Request("Bearer abc", "USER", "work")); // 403
}
}
구성 원칙:
- true를 반환하면 다음으로 위임하고, false를 반환하면 종료하는 정책을 따른다.
- 예외가 발생하면 상위에 공통 에러 핸들러를 추가로 구성하는 게 좋다.
설계·운영에서 챙길 점
공통 관심사는 먼저 배치한다(입력 정규화 → 보안 → 리소스 소모가 큰 작업). 핸들러는 부작용을 최소화하고 상태 공유를 지양한다.
핸들러마다 타임아웃을 두고 전체 SLA 안에서 총합을 관리한다. 실패 즉시 중단(FAIL-FAST)할지 최대한 진행한 뒤 집계 응답을 낼지는 트레이드오프다.
각 핸들러 경계에 로깅/트레이싱 스팬을 넣어 지연 상위 구간을 분석하고, 성공/실패/위임 카운터를 메트릭으로 모은다.
재시도는 멱등성이 보장될 때만 적용하고 백오프·서킷 브레이커를 함께 쓴다. 대체 경로(Fallback)와 알람/에스컬레이션 체계는 따로 분리해 둔다.
프로세스 내 체인은 고성능이지만 단일 프로세스 한계가 있고, 메시지/이벤트 체인은 확장성과 내결함성이 좋은 대신 일관성 지연을 받아들여야 한다.
구현 형태를 비교하면
| 구분 | 성능 | 확장성 | 일관성 | 안정성 | 운영 편의 |
|---|---|---|---|---|---|
| 프로세스 내 체인 | 지연 낮음, 호출 오버헤드 미미 | 수직 확장 중심 | 강한 일관성 | 프로세스 장애에 취약 | 배포/디버깅 용이 |
| 메시지 기반 체인 | 지연 높음(브로커 오버헤드) | 수평 확장 우수 | 최종 일관성 | 재처리/내결함성 우수 | 관찰/운영 복잡성 증가 |