OpenFeign 기여 도전해보기....(하지만 실패한)

Mando·2025년 3월 30일
3

오픈소스

목록 보기
1/3

Spring Cloud OpenFeign 4.1.4에서 부정 매개변수 지원 이슈

부정 매개변수란?

Spring MVC에서 부정 매개변수(! 접두사가 붙은 매개변수)는 URL 매핑을 더 세밀하게 제어하기 위한 기능이다. @RequestMapping(params = {"!expiration"})과 같은 형태로 사용되며, "expiration 매개변수가 없는 요청"을 특정 메서드로 라우팅하는 역할을 한다.

문제 상황

Spring Cloud OpenFeign 4.0.x에서 4.1.4로 업그레이드하면서 다음과 같은 오류가 발생했다:

Caused by: java.lang.IllegalArgumentException: Negated params are not supported: !sync 
at org.springframework.cloud.openfeign.support.SpringMvcContract.parseParams(SpringMvcContract.java:372)

오류 메시지가 명확히 말해주듯, Spring Cloud OpenFeign 4.1.4 버전에서는 부정 매개변수를 더 이상 지원하지 않는다.

부정 매개변수의 역할과 중요성

부정 매개변수는 다음과 같은 중요한 역할을 한다:

  1. 다중 엔드포인트 구분: 동일한 URL 경로에 대해 매개변수 유무에 따라 다른 메서드를 호출할 수 있다.
  2. 선택적 매개변수 처리: 특정 매개변수가 있는 요청과 없는 요청을 각각 다르게 처리할 수 있다.
  3. 모호한 매핑 방지: URL 경로가 같지만 다른 처리 로직이 필요한 경우 명확하게 구분할 수 있다.

아래는 issue에 등록되었던 예제이다:

public interface TokenApi {
    @PostMapping(path = "/api/token", params = {"!expiration"})
    String create();

    @PostMapping(path = "/api/token", params = {"expiration"})
    String create(@RequestParam LocalDateTime expiration);
}

@FeignClient(value = "token", url ="${feign.client.config.token.uri}", contextId = "tokenClient")
public interface TokenClient extends TokenApi {
}

@RestController
public class TokenController implements TokenApi {
    // 구현 내용
}

Spring MVC와 Feign의 근본적 차이

부정 매개변수 지원이 제거된 이유를 이해하려면 두 프레임워크의 차이를 알아야 한다:

  1. Spring MVC (서버 측):

    • 들어오는 HTTP 요청을 적절한 컨트롤러 메서드로 라우팅한다.
    • 부정 매개변수는 "이 매개변수가 없는 요청"을 특정 메서드로 보내는 라우팅 규칙이다.
  2. Feign (클라이언트 측):

    • 서버로 나가는 HTTP 요청을 생성한다.
    • HTTP 프로토콜에서 "매개변수가 없음"을 명시적으로 지정하는 개념이 없다.
    • 매개변수는 URL에 추가하거나 추가하지 않는 두 가지 옵션만 있다.
      - 예를 들어, GET /api/token?expiration=2023-12-31 또는 GET /api/token 중 하나만 가능하다.

이런 차이로 인해 Feign 관점에서 부정 매개변수는 의미가 없어서 지원이 제거되었다.

문제 해결 과정

내가 처음 제안한 해결책

처음에는 다음과 같은 접근 방식을 제안했다:

  1. SpringMvcContract에 부정 매개변수를 허용하는 설정 옵션 추가
  2. 기본값은 false로 설정하여 하위 호환성 유지
  3. 필요한 경우 true로 설정하여 부정 매개변수를 무시하도록 함

이 방식은 사용자에게 더 많은 제어 권한을 주면서 하위 호환성도 유지하는 접근법이라고 생각했다.

메인테이너의 제안과 채택된 해결책

그러나 프로젝트 메인테이너는 더 간단한 접근법을 제안했다: 경고 로그만 남기고 부정 매개변수를 무시하는 방식. 이 방식이 최종적으로 채택되었다.

private void parseParams(MethodMetadata data, Method method, RequestMapping methodMapping) {
    String[] params = methodMapping.params();
    if (params == null || params.length == 0) {
        return;
    }
    for (String param : params) {
        NameValueResolver nameValueResolver = new NameValueResolver(param);
        if (!nameValueResolver.isNegated()) {
            data.template().query(resolve(nameValueResolver.getName()), resolve(nameValueResolver.getValue()));
        }
        else {
            // 예외를 발생시키는 대신 경고 로그만 남긴다
            log.warn("Negated param '{}' is ignored in Feign client. This may cause unexpected behavior " +
                    "if you're sharing interfaces between server and client.", param);
            // 부정 매개변수는 무시된다
        }
    }
}

왜 로그만 남겨도 충분한가?

처음에는 설정 옵션을 추가하는 방식을 제안했지만, 더 깊이 생각해보니 로그만 남기는 간단한 해결책으로도 충분했다. 그 이유는:

  1. 부정 매개변수는 클라이언트 측에서 실제로 의미가 없다. Feign이 HTTP 요청을 생성할 때는 매개변수를 포함하거나 포함하지 않는 두 가지 방법만 있다. 부정 매개변수는 단지 "이 매개변수를 포함하지 마라"라는 의미인데, 이는 결국 매개변수를 추가하지 않는 것과 동일하다.

  2. HTTP 요청 자체는 변하지 않는다. 예외를 던지든 로그만 남기든, 결과적으로 생성되는 HTTP 요청은 같다. 어차피 부정 매개변수가 있으면 해당 매개변수를 제외하고 요청을 만들기 때문이다.

  3. 서버 측 동작은 영향 받지 않는다. Spring MVC에서는 여전히 부정 매개변수를 정상적으로 처리한다.

이번 이슈를 통해 배운 점

이 이슈를 해결하면서 몇 가지 중요한 점을 배웠다:

  1. 문제의 본질을 파악하는 중요성: 처음에는 부정 매개변수를 지원하는 설정 옵션을 추가하는 방향으로 생각했지만, 결국 문제의 본질은 "부정 매개변수가 클라이언트에서 어차피 의미가 없다"는 점이었다. 근본 원인을 제대로 이해하면 더 적절한 해결책을 찾을 수 있다.

  2. 기능 제거의 의도 이해하기: Spring Cloud OpenFeign에서 부정 매개변수 지원을 제거한 것은 단순히 기능을 없애려는 게 아니었다. 서버는 요청을 받아 처리하는 역할, 클라이언트는 요청을 보내는 역할이라는 각자의 책임을 명확히 하기 위한 결정이었다. 클라이언트에서는 어차피 "이 매개변수가 없는 요청"이란 개념이 실제로 필요 없기 때문이다. 이런 의도를 이해하니 더 적절한 해결책을 찾을 수 있었다.

  3. 하위 호환성과 실용성의 균형: 때로는 엄격한 하위 호환성(예외 발생)보다 실용적인 접근(경고 로그)이 사용자에게 더 도움이 될 수 있다. 특히 실제 동작에 영향이 없는 경우에는 더욱 그렇다.

  4. 오픈소스 협업의 가치: 메인테이너와의 논의를 통해 더 간단하고 효과적인 방법을 발견할 수 있었다. 이것이 오픈소스 커뮤니티의 가치인가

  1. 테스트 코드의 중요성: 오픈소스 프로젝트의 방대한 테스트 코드는 정말 큰 도움이 되었다. 이 테스트가 없었다면 내가 추가한 코드가 제대로 작동할지 확신하기 어려웠을 것이다. 기존 테스트는 내가 미처 생각하지 못한 상황까지 검증하도록 요구했고, 이를 통해 테스트 대상에 대한 새로운 관점도 얻을 수 있었다. 메인테이너는 내가 생각지도 못한 엣지 케이스에 대한 테스트를 요구하기도 했는데, 이런 과정이 내가 바라보는 테스트 대상에 대한 관점도 실력 향상에도 큰 도움이 되었다.

GitHub PR 링크에서 확인할 수 있다.

0개의 댓글