Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[REFACTOR] AOP를 통한 쿠키 관리 로직 분리 #444

Open
5 tasks
jhon3242 opened this issue Dec 26, 2024 · 1 comment
Open
5 tasks

[REFACTOR] AOP를 통한 쿠키 관리 로직 분리 #444

jhon3242 opened this issue Dec 26, 2024 · 1 comment
Assignees
Labels
♻️ refactor 리팩토링

Comments

@jhon3242
Copy link
Member

jhon3242 commented Dec 26, 2024

Description 💭

쿠키를 발급하는 로직을 RoomMemberCookieEncryptor와 같은 필드로 컨트롤러에 가지고 있고, 쿠키를 설정하고 지우는 작업을 컨트롤러가 가지고 있는 것은 책임 분리 측면에서 부적절하다고 판단하였다. 이에 따라, 쿠키 설정 및 삭제 로직을 컨트롤러에서 분리하여 관리할 필요성을 느끼게 되었다.

TODO ✅

  • ArgumentResolver를 활용한 memberId 캐스팅 구현
  • AOP를 활용한 쿠키 발급 구현
  • 해당 기능들에 대한 테스트 구현
  • 기존 레거시 코드 삭제
  • API 문서 테스트 수정

Reference 🔎

배경

방 참여방 생성 작업 시 쿠키를 발급하여 유저 정보를 세션 관리하고 있었다.

image

(방 참여 API에서 쿠키를 저장하는 로직을 호출한다.)

하지만, 쿠키를 발급하는 로직을 RoomMemberCookieEncryptor와 같은 필드로 컨트롤러에 가지고 있고, 쿠키를 설정하고 지우는 작업을 컨트롤러가 가지고 있는 것은 책임 분리 측면에서 부적절하다고 판단하였다. 이에 따라, 쿠키 설정 및 삭제 로직을 컨트롤러에서 분리하여 관리할 필요성을 느끼게 되었다.

image

(RoomController에서 쿠키를 설정하고 삭제하는 로직이 구현되어 있다)

이를 해결하기 위해 다음 세 가지 방안을 고려하였다.

대안

1. Filter

필터는 J2EE 표준 스펙으로, 디스패처 서블릿에 전달되기 전후에 URL 패턴에 맞는 모든 요청에 대해 부가 작업을 처리할 수 있는 기능을 제공하였다.

image

필터를 사용하면, 컨트롤러로 요청이 전달되기 이전에 HTTP 요청에서 쿠키를 확인하거나 요청 처리 이후에 쿠키를 발급하는 로직을 구현할 수 있었다.

[필터를 활용하면 스프링 예외 처리를 사용할 수 없다.]

필터 내에서 발생하는 예외는 스프링의 ControllerAdvice나 ExceptionHandler를 통해 처리할 수 없었다. 이는 예외가 서블릿에게 전달되기 전에 발생하기 때문이었다. [참고](https://mangkyu.tistory.com/204)

필터를 사용하면 빈을 사용하지 못하는거 아니야?

필터는 스프링 이전의 서블릿 영역에서 관리된다. 따라서 스프링에서 관리하는 빈을 사용하지 못할 것이라고 생각할 수 있다. 예전에는 그랬었다. 하지만 현재 스브링 빈으로 등록이 가능하며, 다른 곳에서 주입되거나 다른 빈을 주입받을 수 있다.

인터셉터

인터셉터는 Spring이 제공하는 기술로, 디스패처 서블릿이 컨트롤러를 호출하기 전과 후에 요청과 응답을 참조하거나 가공할 수 있는 기능을 제공한다. 즉, 스프링 컨텍스트 내에서 동작한다.

image

[스프링 예외 처리를 사용할 수 있다.]

스프링 컨텍스트에서 동작하기 때문에 ControllerAdvice나 ExceptionHandler와 같은 스프링 예외 처리를 활용할 수 있다.

[인터셉터를 활용하면 요청 / 응답 객체를 교체할 수 없다.]

인터셉터는 필터와 다르게 인터셉터 목록을 가지고 있고, for문을 순차적으로 돌면서 실행시킨다. 인터셉터가 하나씩 실행되고 true를 반환해야 다음 인터셉터가 실행된다. 만약 중간에 하나의 인터셉터라고 false를 반환하게 되면 요청이 중단된다.

즉, 인터셉터는 요청과 응답 객체를 완전히 교체할 수 없으며, 단순히 참조하거나 조작할 수 있다.

public class MyInterceptor implements HandlerInterceptor {

    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // Request/Response를 교체할 수 없고 boolean 값만 반환할 수 있다.
        return true;
    }
}

[Path 패턴 단위로만 필터를 설정할 수 있다.]

public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(cookieInterceptor)
            .addPathPatterns("/api//balances/rooms"); // path 패턴으로 필터
}

인터셉터를 등록할 때 적용될 범위를 설정해줄 수 있다. 이때는 Path의 패턴으로만 설정할 수 있다.
즉, 특정 HTTP Method만 인터셉터가 적용되게 하려면 아래와 같이 별도의 방법을 사용해야한다.

public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
            Object handler) {

        final String method = request.getMethod();
        if(method.equals(HttpMethod.POST) || method.equals(HttpMethod.PUT)){
            String token = AuthorizationExtractor.extract(request);
            if (jwtTokenProvider.validateToken(token)) {
                return true;
            }
            throw new UnauthorizedUserException();
        }
        return true;
    }
}

인터셉터를 정의하는 객체에서 request 객체에서 method를 추출하여 검증을 해주는 방법을 활용할 수 있다.
하지만 인터셉터의 적용 범위를 설정하는 곳이 두 곳으로 나뉘게 된다.

  • Path 설정 → Config 객체
  • Method 설정 → Interceptor 객체

설정의 범위가 분산되어 있는 것은 유지보수에 좋은 영향을 끼치지 않는다.

AOP

AOP는 관점 지향 프로그래밍 패러다임으로, 여러 모듈에서 공통적으로 사용되는 관점을 분리하는 프로그래밍 패턴이다. Spring AOP는 메서드 호출, 예외 발생 등 특정 이벤트에 대해 공통 기능(로깅, 트랜잭션 처리 등)을 적용할 수 있게 도와준다.

image

[확장성 및 재사용성]

메서드 호출 전후, 예외 발생 시 등 다양한 시점에 로직을 삽입할 수 있으며, 특정 패턴이나 애노테이션에 따라 공통 로직을 재사용할 수 있다.

[메서드 단위로 설정 범위를 설정할 수 있다.]

인터셉터보다 더 세부적인 범위를 설정할 수 있다.
예) GET 요청에만 인터셉터 적용

[코드의 복잡성이 올라간다]

커스텀 애노테이션 추가 및 AOP 설정이 필요하여 코드의 복잡성이 증가할 수 있었다.

[성능 오버헤드]

프록시 생성 등으로 인해 약간의 성능 저하가 발생할 수 있다.

결론

필터, 인터셉터, AOP 세 가지 방안을 검토한 결과, AOP를 활용하는 방식이 가장 적합하다고 판단한다. 그 이유는 다음과 같다.

  1. 스프링 예외 처리를 활용할 수 있다: 인터셉터는 Spring 컨텍스트 내에서 동작하기 때문에, ControllerAdvice와 ExceptionHandler를 통한 일관된 예외 처리가 가능하다.
  2. 메서드 단위 필터를 활용할 수 있다: AOP는 포인트 컷을 활용하여 특정 메서드에서만 AOP가 적용되게 설정할 수 있다. 인터셉터도 불가능한 것은 아니지만 관리 영역이 분산되어 유지보수에 악영향을 끼친다.

필터는 스프링 예외 처리를 활용하기 어려운 점이 큰 단점으로 작용하며, 인터셉터를 활용하면 관리 영역이 분산된다고 판단했다. 따라서, AOP를 활용하여 쿠키 설정 및 삭제 로직을 분리하는 방안을 채택하기로 결정했다.

참조

[[Spring] 필터(Filter) vs 인터셉터(Interceptor) 차이 및 용도 - (1)](https://mangkyu.tistory.com/173)

[[Java Spring] Filter, Interceptor, AOP의 차이](https://kimdirector1090.tistory.com/129)

@jhon3242 jhon3242 added the ♻️ refactor 리팩토링 label Dec 26, 2024
@jhon3242 jhon3242 self-assigned this Dec 26, 2024
@leegwichan
Copy link
Contributor

Interceptor가 요청별로 관리하고 있어 구현하기 좀 더 편해 보이는데, 여러 내용을 비교해서 최선의 방법을 선택한 타칸 최고 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
♻️ refactor 리팩토링
Projects
Status: No status
Development

No branches or pull requests

2 participants