책임 연쇄 패턴(Chain of Responsibility Pattern)

wannabeking·2023년 4월 6일
0

디자인 패턴

목록 보기
14/14
post-thumbnail

프로젝트 진행 중, Refresh Token에 대한 여러 차례의 연속적인 검증을 구현했습니다.

해당 코드는 다음과 같았습니다.


RefreshTokenService

if(검증1) {
	log...
    throw exception...
}

if(검증2) {
	log...
    throw exception...
}

...

위 코드의 문제점은 검증 코드가 바뀔 때마다 Service를 수정해야 하므로, SRP를 위반한다는 점입니다.

RefreshTokenService의 경우 Domain Layer로 Domain Obejct(해당 프로젝트에서는 Entity와 동일 시)의 비즈니스 로직을 처리하는 역할을 담당하고 있습니다.

물론 Validation을 비즈니스 로직의 일부로 볼 수 있지만, 그렇게 된다면 Service에서 너무 많은 일을 하게 됩니다.

또한 여러 차례의 검증 과정이 메소드에 포함되고, 함수로 빼낸다고 하더라도 전체 소스코드의 양은 줄어들지 않습니다.


따라서 저는 Validation을 담당하는 클래스를 운용하기로 했고, 책임 연쇄 패턴을 사용하고자 했습니다.

연속적인 Validation에 책임 연쇄 패턴을 사용하는 이유는, 하나의 Validator에서 검증이 완료되면 다음 Validator에게 검증의 책임을 넘기기 때문입니다.


우선 책임 연쇄 패턴에 대해 간단하게 알아보고, 예시를 살펴보겠습니다.



책임 연쇄 패턴

책임 연쇄 패턴은 객체 간의 결합성을 낮추기 위해 사용됩니다.

이는 요청을 보내는 객체와 이를 처리하는 객체 간의 직접적인 의존성을 없애는 것입니다.

요청 객체는 처리하는 객체들이 어떻게 구현되어 있는지 알 필요가 없으며, 해당 객체들이 구성된 Chain을 호출하기 때문에 직접적인 의존성이 없어지게 됩니다.


그렇다면 처리하는 객체들이 어떻게 Chain으로 구성되는지 프로젝트에서 사용한 Validation Chain으로 알아보겠습니다.


public class ChainValidator<T> {

    private final T objectToValidate;
    private final List<Validator<T>> validators = new ArrayList<>();
    private final List<Runnable> actionsOnError = new ArrayList<>();

    public ChainValidator(T objectToValidate) {
        this.objectToValidate = objectToValidate;
    }

    public ChainValidator<T> next(Validator<T> validator, Runnable actionOnError) {
        validators.add(validator);
        actionsOnError.add(actionOnError);
        return this;
    }

    public void execute() {
        for (int i = 0; i < validators.size(); i++) {
            if (!validators.get(i).validate(objectToValidate)) {
                actionsOnError.get(i).run();
                break;
            }
        }
    }
}

위와 다른 구현의 예시로 Chain이 처리 객체들을 필드로 가지는 것이 아닌, Chain을 처리 객체 자체의 interface로 만드는 방법도 있습니다.

저는 Validator에서 검증에 실패한 경우, log를 찍고 Exception을 던질 것이기 때문에 Runnable List를 필드에 선언하겠습니다.


public interface Validator<T> {

    boolean validate(T objectToValidate);
}

위와 같이 Validator 인터페이스를 만들어 검증을 수행하는 Validator들을 작성합니다.


@Slf4j
public class RefreshTokenValidator {

    private final ChainValidator<RefreshTokenObjectToValidate> chainValidator;

    public RefreshTokenValidator(RefreshTokenObjectToValidate objectToValidate) {
        chainValidator = new ChainValidator<>(objectToValidate)
            .next(this::isValidRefreshToken, () -> {
                log.warn("유효하지 않은 Refresh Token 사용");
                throw new CustomException(ACCESS_TOKEN_REISSUE_FAIL);
            })
            .next(this::isAliveRefreshToken, () -> {
                log.warn("차단된 Refresh Token 사용");
                throw new CustomException(ACCESS_TOKEN_REISSUE_FAIL);
            })
            .next(this::isSameRefreshToken, () -> {
                log.warn("발급하지 않은 Refresh Token 사용");
                throw new CustomException(ACCESS_TOKEN_REISSUE_FAIL);
            });
    }

    public void execute() {
        chainValidator.execute();
    }

    private boolean isValidRefreshToken(RefreshTokenObjectToValidate objectToValidate) {
        DecodedJWT decodedRefreshToken = objectToValidate.getDecodedRefreshToken();
        return decodedRefreshToken.getExpiresAt().after(new Date()) &&
            "reach-rich".equals(decodedRefreshToken.getIssuer());
    }

    private boolean isAliveRefreshToken(RefreshTokenObjectToValidate objectToValidate) {
        return objectToValidate.getMaybeRefreshToken().isPresent();
    }

    private boolean isSameRefreshToken(RefreshTokenObjectToValidate objectToValidate) {
        return objectToValidate.getMaybeRefreshToken().get()
            .isSameValue(objectToValidate.getRefreshToken());
    }
}

그럼 이제, ChainValidatorValidator들을 구현해서 연결해주는 클래스를 작성합니다.

생성자를 통해 검증할 객체를 전달 받고 validators chain을 구현합니다.

execute 메소드가 호출되면 구현한 chain을 실행하여 검증을 진행합니다.

과정 중에서 검증에 실패한다면 error 상황에서 실행할 Runnable 객체를 동작시킬 것이며, 검증에 성공한다면 바로 다음 Validator의 검증이 실행될 것입니다.

또한 해당 검증이 여러 번 사용된다면, 익명 클래스를 사용하지 않고 하나의 클래스로 작성하여 여러 번 재사용할 수 있을 것입니다.



profile
내일은 개발왕 😎

0개의 댓글