스프링 부트에서 Feign 클라이언트 사용하기: 파라미터 처리 방식 이해하기
이전 글에서 Feign 클라이언트를 사용하면서 컨트롤러에서 파라미터가 계속 null로 전달되어 어려움을 겪었다. 이부분에 대해서 정리를 좀 해야겠다.
스프링 부트에서 Feign 클라이언트를 사용할 때, 다양한 유형의 파라미터들을 전달하는 방법에 대해 알아보자.
주로 다룰 파라미터 유형에는 @RequestParam과 @RequestBody 등이 있다.
어노테이션은 Feign 클라이언트에서 HTTP 요청을 구성하는 데 중요한 역할을 한다.
1. Feign 클라이언트에서 @RequestParam 어노테이션 생략 시 발생 가능한 문제
1-1. 매핑 문제
- 설명
- @RequestParam 없이 Long memberId와 같이 파라미터를 전달하면, Feign 클라이언트는 이 파라미터가 요청의 어느 부분에 해당하는지 명확히 알 수 없다. 이로 인해 서버 측에서 쿼리 파라미터로 기대하는 값이 전달되지 않을 수 있다.
- 예시 에러
- MissingServletRequestParameterException이 발생할 수 있으며, 이는 필수 쿼리 파라미터가 누락됐음을 의미한다.
1-2. 요청-응답 불일치
- 설명
- 클라이언트와 서버 간의 계약(contract)에 따라, 파라미터는 일정한 방식으로 전송되어야 한다.
- @RequestParam 생략으로 인해 서버가 요청을 올바르게 해석하지 못하면 의도하지 않은 방식으로 데이터가 처리될 수 있다.
1-3. 가독성과 유지보수 문제
- 설명
- 명확한 어노테이션 사용은 코드의 가독성을 높이고 의도를 분명히 한다.
- 어노테이션 생략은 다른 개발자들이나 미래의 자신이 코드를 이해하는데 어려움을 줄 수 있다.
2. 단일 파라미터 처리: @RequestParam 사용
- 특징과 사용법
- @RequestParam은 주로 단일 파라미터를 쿼리 스트링으로 전송할 때 사용된다.
- 이 어노테이션은 메서드 파라미터가 HTTP 요청의 쿼리 파라미터임을 나타낸다.
- 예를 들어, getNickname(@RequestParam(name = "memberId") Long memberId) 메서드는 memberId라는 이름의 쿼리 파라미터를 필요로 한다.
- 장점
- 명확한 쿼리 파라미터 매핑을 제공하고, 요청 URL이 길지 않은 경우에 효과적이다.
- 주의점
- URL 길이에 제한이 있으므로, 많은 양의 데이터를 전송하는 데는 적합하지 않다.
@FeignClient(name = "example-service", url = "${feign.example-service.url}")
public interface ExampleFeignClient {
/**
* 단일 파라미터 'userId'를 쿼리 파라미터로 전송하는 예시
*/
@GetMapping("/user/details")
UserDetails getUserDetails(@RequestParam("userId") Long userId);
}
- @RequestParam("userId") Long userId는 HTTP 요청에서 userId라는 이름의 쿼리 파라미터를 Long 타입의 userId 메서드 파라미터로 매핑한다.
- @GetMapping("/user/details")는 HTTP GET 요청을 /user/details 경로로 매핑한다.
3. 복잡한 파라미터 처리: @RequestBody 사용
- 특징과 사용법
- @RequestBody 어노테이션은 복잡한 객체를 JSON 형태로 요청 본문에 담아 전송할 때 사용된다.
- 예를 들어, 사용자 정보를 담은 객체를 전송하는 경우 updateUser(@RequestBody User user) 같은 형태로 사용된다.
- 장점
- 복잡한 데이터 구조를 쉽게 전송할 수 있으며, 데이터의 양에 대한 제한이 적다.
- 주의점
- @RequestBody는 한 번의 요청에 하나만 사용할 수 있으며, GET 요청에는 적합하지 않다.
@FeignClient(name = "example-service", url = "${feign.example-service.url}")
public interface ExampleFeignClient {
/**
* 복잡한 객체 'userUpdateRequest'를 요청 본문으로 전송하는 예시
*/
@PutMapping("/user/update")
UserResponse updateUser(@RequestBody UserUpdateRequest userUpdateRequest);
}
- @RequestBody UserUpdateRequest userUpdateRequest는 UserUpdateRequest 타입의 객체를 JSON 형태로 요청 본문에 담아 전송한다.
- @PutMapping("/user/update")는 HTTP PUT 요청을 /user/update 경로로 매핑한다.
4. 혼합 파라미터 사용: @RequestParam과 @RequestBody 동시 사용
- 특징
- 때때로, 요청에서 단일 파라미터와 복잡한 객체를 동시에 전송해야 할 경우가 있다.
- 이럴 때 @RequestParam과 @RequestBody를 함께 사용할 수 있다.
- 예시
- createPost(@RequestParam("userId") Long userId, @RequestBody PostData postData)와 같이, 사용자 ID는 쿼리 파라미터로, 게시글 데이터는 요청 본문으로 전송될 수 있다.
- 주의점
- 이 방식은 주로 POST나 PUT 요청에서 사용되며, 요청의 구조가 복잡해질 수 있어 주의가 필요하다.
@FeignClient(name = "example-service", url = "${feign.example-service.url}")
public interface ExampleFeignClient {
/**
* 'userId'를 쿼리 파라미터로, 'newPost' 객체를 요청 본문으로 전송하는 예시
*/
@PostMapping("/post/create")
PostResponse createPost(@RequestParam("userId") Long userId, @RequestBody NewPost newPost);
}
- @RequestParam("userId") Long userId와 @RequestBody NewPost newPost를 사용해, userId는 쿼리 파라미터로, NewPost 객체는 요청 본문으로 전송한다.
- @PostMapping("/post/create")는 HTTP POST 요청을 /post/create 경로로 매핑한다.
5. @PathVariable 사용
- 특징
- URI 경로 변수 매핑: @PathVariable은 URI 경로의 일부를 컨트롤러 메서드의 파라미터로 직접 매핑한다. 이를 통해 동적인 URL 부분을 쉽게 처리할 수 있다.
- RESTful URL 지원: REST API에서 자주 사용되며, 리소스 식별자로서 URL 경로 변수를 사용하는 것을 가능하게 한다.
- 사용법
- 변수 바인딩: 메서드의 파라미터에 @PathVariable을 사용하여 URL 경로의 변수를 직접 바인딩한다.
- 동적 URL 처리: 예를 들어, 사용자의 세부 정보를 가져오는 URL인 /users/{userId}에 대해, {userId} 부분은 메서드 파라미터로 전달되어 사용된다.
- 주의점
- 변수명 일치: @PathVariable 어노테이션의 변수명과 메서드 파라미터의 이름이 일치해야 한다. 불일치할 경우 바인딩이 제대로 이루어지지 않는다.
- 예외 처리: 존재하지 않거나 유효하지 않은 경로 변수에 대한 요청이 들어올 경우, 적절한 예외 처리가 필요하다.
@FeignClient(name = "userService")
public interface UserClient {
@GetMapping("/users/{userId}")
User getUserById(@PathVariable("userId") Long userId);
}
- @GetMapping("/users/{userId}")는 userId 값을 URL 경로에서 추출하여 getUserById 메서드의 userId 파라미터로 전달한다.
6. 마무리
이번 경험을 통해, 때로는 가장 간단한 해결책이 가장 큰 차이를 만들 수 있음을 깨달았다.
단순히 적절한 어노테이션(@RequestParam)을 붙이는 것만으로도 문제가 해결될 수 있다는 사실을 간과했다.
Long 타입의 memberId만 파라미터로 전달하며, 'memberId = null'이라는 오류와 계속 마주쳤던 나의 경험은, 때때로 가장 기본적인 것들이 얼마나 중요한지를 상기시켜준다.
이러한 작은, 하지만 중요한 디테일을 간과해서 발생한 이슈는, 앞으로 비슷한 실수를 반복하지 않기 위한 교훈이 되었다. 앞으로는 이러한 기본적인 사항들에 더욱 주의를 기울이며, 개발 과정에서 발생할 수 있는 문제들에 대해 더 깊이 이해하고자 한다.
스프링 부트에서 Feign 클라이언트 요청 적용하는 방법은 아래 글에 나와있다.
[Spring Boot] Spring Boot에서 Feign 클라이언트 사용하기(@FeignClient 사용 가이드)
실제 이 Feign 클라이언트가 사용되는 MSA 아키텍처가 궁금하다면 아래 글을 보면 좋을 것 같다.
[Spring Boot] MSA 환경에서 SNS/SQS 활용하기: 서버간 DB 동기화와 제로 페이로드 방식의 효과적 구현
이 포스트는 Team chillwave에서 사이드 프로젝트 중 적용했던 부분을 다시 공부하며 기록한 것입니다.
시간이 괜찮다면 팀원 '개발자의 서랍'님의 블로그도 한번 봐주세요 :)