GoF Chain of Responsibility 패턴: 요청 처리를 위한 연결 고리 설계

Chain of Responsibility 패턴은 행동(Behavioral) 디자인 패턴 중 하나로, 요청을 보내는 객체(Client)와 이를 처리하는 객체(Handler) 사이의 결합도를 낮추는 데 목적이 있습니다. 이 패턴은 여러 개의 핸들러(Handler) 객체를 연결하여 하나의 체인(Chain)을 형성하고, 요청이 이 체인을 따라 순차적으로 전달되도록 합니다. 각 핸들러는 요청을 처리할 수 있는지 스스로 판단하며, 처리 가능한 경우 요청을 소비하고 체인을 종료합니다. 처리할 수 없는 경우 다음 핸들러로 요청을 전달합니다.

1. Chain of Responsibility 패턴의 동작 원리

이 패턴의 핵심은 요청의 발신자와 수신자를 분리하는 데 있습니다. 발신자는 특정 핸들러를 직접 호출하는 대신 체인의 첫 번째 핸들러에게 요청을 보내기만 하면 됩니다. 핸들러는 자신의 책임 범위를 확인하고, 요청을 처리하거나 다음 핸들러로 넘깁니다.

주요 구성 요소:

  • Handler (핸들러 인터페이스/추상 클래스): 요청을 처리하는 메서드를 정의하고, 다음 핸들러에 대한 참조(Reference)를 유지합니다.
  • ConcreteHandler (구체적인 핸들러): 실제 요청 처리 로직을 구현합니다. 자신이 처리할 수 있으면 처리하고, 그렇지 않으면 체인의 다음 핸들러에게 요청을 위임합니다.
  • Client (클라이언트): 핸들러 체인을 구성하고, 체인의 첫 번째 핸들러에게 요청을 보냅니다.

2. Decorator 패턴 및 Iterator 패턴과의 비교

Chain of Responsibility 패턴은 다른 패턴과 구조적 유사성을 보이기도 하지만, 목적과 동작 방식에서 차이가 있습니다.

특징Chain of ResponsibilityDecorator
목적요청 발신자와 수신자 간의 결합도 감소, 여러 핸들러가 요청을 순차적으로 처리객체에 동적으로 새로운 책임(기능) 추가
구조요청이 체인을 따라 전달되며, 각 핸들러는 다음 핸들러 참조를 가짐원본 객체를 래핑(Wrapping)하여 기능을 확장
요청 전달요청은 체인을 따라 전달되며, 특정 핸들러가 처리하거나 체인 끝까지 전달됨요청 전달 없이, 래핑된 객체에 기능을 추가함
핵심요청 처리 순서 및 중단 조건 관리객체의 행위를 동적으로 확장 (OCP 원칙 준수)
사용 예로깅 시스템, 승인 워크플로우, 미들웨어 파이프라인스트림 래퍼(IO Stream), UI 컴포넌트 기능 추가

3. 패턴의 장점과 단점

장점

  • 결합도 감소: 요청 발신자는 특정 핸들러에 대한 의존성 없이 체인에 요청을 전송합니다.
  • 유연성 및 확장성: 체인에 새로운 핸들러를 추가하거나 기존 핸들러의 순서를 변경하는 것이 용이합니다.
  • 책임 분리 (SRP): 각 핸들러는 자신의 책임(특정 조건 검증 또는 처리)에만 집중할 수 있습니다.
  • 동적 구성: 런타임에 핸들러 체인을 동적으로 구성할 수 있습니다.

단점

  • 성능 저하 가능성: 체인이 길어지면 요청이 여러 핸들러를 거쳐야 하므로 성능에 영향을 줄 수 있습니다.
  • 디버깅 복잡성: 어떤 핸들러가 요청을 처리했는지, 또는 처리되지 않았는지 추적하기 어려울 수 있습니다.
  • 요청 미처리 가능성: 적절한 핸들러가 체인에 존재하지 않으면 요청이 처리되지 않고 종료될 수 있습니다.

4. 실제 적용 사례

Chain of Responsibility 패턴은 다양한 시스템에서 활용됩니다.

  • 로깅 시스템: 로그 레벨(DEBUG, INFO, WARN, ERROR)에 따라 다른 핸들러가 로그를 처리하도록 구성할 수 있습니다.
  • 승인 워크플로우: 팀장, 부장, 이사 순으로 승인 권한을 위임하는 시스템을 구현할 수 있습니다.
  • 보안 인증 체인: HTTP 요청이 특정 리소스에 접근하기 전에 인증(Authentication), 권한 부여(Authorization), 입력 검증(Validation) 단계를 순차적으로 거치도록 구성할 수 있습니다.
  • 이벤트 버블링 (JavaScript DOM): 이벤트가 발생한 요소에서 상위 요소로 전파되는 방식을 설명합니다.

5. Spring Boot 기반 구현 예제: 신용 평가 시스템

아래는 Spring 프레임워크를 사용하여 Chain of Responsibility 패턴을 구현한 예제입니다. 각 핸들러는 Spring Bean으로 관리되며, 체인은 자동으로 구성됩니다.

5.1. Handler 인터페이스 정의

public interface RuleHandler {
    // 요청 처리 성공 시 true, 실패 시 false 반환
    boolean evaluate(RequestContext request);
}

5.2. 요청 객체(Request Context) 정의

import lombok.Data;

@Data
public class RequestContext {
    private String userId;
    private Integer creditScore;
    private Double loanAmount;
    // 기타 필요한 필드
}

참고: Lombok의 @Data 어노테이션은 Getter, Setter, toString 등을 자동 생성합니다.

5.3. 구체적인 Rule 핸들러 구현

각 핸들러는 `@Component`와 `@Order` 어노테이션(또는 Ordered 인터페이스 구현)을 통해 순서를 지정할 수 있습니다. 아래 예제는 `@Order`를 사용합니다.

import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Order(1)
public class IdentityValidationRule implements RuleHandler {
    @Override
    public boolean evaluate(RequestContext request) {
        if (request.getUserId() == null || request.getUserId().isBlank()) {
            System.err.println("[실패] 사용자 ID 확인: 유효하지 않음");
            return false;
        }
        System.out.println("[성공] 사용자 ID 확인 완료");
        return true;
    }
}

@Component
@Order(2)
public class BlacklistCheckRule implements RuleHandler {
    @Override
    public boolean evaluate(RequestContext request) {
        // 가상의 블랙리스트 사용자 ID
        if ("blocked_user_01".equals(request.getUserId())) {
            System.err.println("[실패] 블랙리스트 확인: 차단된 사용자");
            return false;
        }
        System.out.println("[성공] 블랙리스트 확인 완료");
        return true;
    }
}

@Component
@Order(3)
public class CreditScoreRule implements RuleHandler {
    @Override
    public boolean evaluate(RequestContext request) {
        if (request.getCreditScore() != null && request.getCreditScore() < 650) {
            System.err.println("[실패] 신용 점수 확인: 기준 미달 (" + request.getCreditScore() + ")");
            return false;
        }
        System.out.println("[성공] 신용 점수 확인 완료");
        return true;
    }
}

5.4. 핸들러 체인 관리자 (Chain Manager)

Spring을 사용하면, `List`를 주입받아 순서대로 실행하는 관리자 클래스를 만들 수 있습니다.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;

@Service
public class RuleChainExecutor {

    @Autowired
    private List<RuleHandler> ruleHandlers; // Spring이 @Order 순서로 정렬하여 주입

    public boolean executeChain(RequestContext request) {
        for (RuleHandler handler : ruleHandlers) {
            boolean isSuccess = handler.evaluate(request);
            if (!isSuccess) {
                System.out.println("요청 처리 실패, 체인 중단.");
                return false; // 실패 시 체인 중단
            }
        }
        System.out.println("모든 규칙 통과 완료!");
        return true;
    }
}

5.5. 클라이언트 실행 코드

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class ChainOfResponsibilityTest {

    @Autowired
    private RuleChainExecutor executor;

    @Test
    public void testChain() {
        RequestContext req = new RequestContext();
        req.setUserId("user_001");
        req.setCreditScore(700);
        req.setLoanAmount(10000.0);

        executor.executeChain(req); // 기대: 성공
    }

    @Test
    public void testChainFailure() {
        RequestContext req = new RequestContext();
        req.setUserId("blocked_user_01");
        req.setCreditScore(600);
        req.setLoanAmount(50000.0);

        executor.executeChain(req); // 기대: 블랙리스트에서 실패
    }
}

태그: Chain of Responsibility 디자인 패턴 Spring Boot 소프트웨어 설계 java

5월 24일 06:02에 게시됨