안녕하세요!
이번 게시글에선 일반적인 애플리케이션에서 Http 응답에 대한 방식을 어떻게 설정할지에 대해 알아보겠습니다.
스프링 프로젝트인 경우 보통 @ResponseBody 어노테이션, ResponseEntity 클래스, 커스텀 응답 클래스를 사용하는데요. 각각에 대해 설명해보겠습니다.
@ResponseBody - HTTP 규격에 body만 지정할 수 있다.
스프링에서 제공해주는 @ResponseBody 어노테이션입니다.
HTTP 규격에 맞춰 컨트롤러의 리턴 값을 Boby에 담아주는 역할을 합니다.
특히 API 서버로 구성된 프로젝트인 경우 컨트롤러에 @RestController 어노테이션을 설정하는 경우가 대부분인데
@RestController 에 @ResponseBody가 부착되어 있으므로 해당 경우에 별도 설정을 하지 않아도 자동 적용됩니다.
응답값의 예시를 실제로 확인해볼까요?
@RestController 설정된 컨트롤러에서 POJO DTO의 리스트를 요청해보겠습니다.
응답값으로 일반적인 json 형식의 값이 HTTP 바디에 담겨 내려왔습니다.
@ResponseBody는 내부적으로 HttpMessageConverter를 사용하는데요. @ResonseBody가 붙은 메서드의 응답값을 Jackson 라이브러리를 통해 json 형식으로 변환해줍니다.
ResponeEntity 클래스 - HTTP 규격에 header, body, status를 지정할 수 있다
다음으로 알아볼 것은 ResponseEntity 클래스입니다.
ResponesEntity는 스프링에서 제공해주는 객체로 HttpEntity 클래스를 상속받았습니다.
ResponesEntity는 HttpEntity의 필드인 headers, body 필드를 상속받고, 자신의 필드인 status를 갖고 있습니다.
위의 자바독을 보면 RestTemplate, @Controller에서 사용된다고 명시되어 있습니다.
하지만 실무에서는 그 외의 일반적인 응답값에도 ResponseEntity를 담아 리턴하는 경우가 많은 것 같습니다.
그 이유는 HTTP의 body만 담을 수 있는 @ResponseBody와 달리,
개발자가 ResponseEntity를 활용하여 headers, status를 수정해줄 수 있기 때문입니다.
예시를 보실까요?
아래와 같은 상황은 일반적으로 에러 상황으로 에러 상태코드를 맞게 응답의 status로 전달해야합니다.
problem) 에러 상황임에도 상태코드 200으로 응답된다.
@ResponseEntity는 body만 담아주기 때문에 body의 필드로 code:400 값을 넣어준 것과는 별개로 HTTP status 200 응답이 내려왔습니다. 왜 일까요?
발생된 에러는 @ControllerAdvice를 통하여 에러가 핸들링 되었습니다.
핸들링 메서드 내부에서 에러 응답 객체를 생성해 body에 넣어주었지만 발생한 에러는 핸들링 되었으므로
스프링은 정상 로직으로 200 응답값을 내려주고 있던 것 입니다..!
위 상황에선 두가지 방식으로 기대했던 status를 내려줄 수가 있는데요.
solve 1 ) @ResponseStatus 설정으로 상태코드 설정하기
첫 번째 방식은 @ResponseStatus 설정입니다.
해당 어노테이션을 통하여 HTTP status에 특정 값을 지정해줄 수 있습니다.
solve 2 ) ResponseEntity 클래스로 상태코드 설정하기
위처럼 ResponseEntity 내부의 status를 직접 수정하는 방법이 있습니다.
저는 ResponseEntity가 제공해주는 팩터리 메서드를 사용하여 좀 더 깔끔한 처리를 해보았습니다.
@ResponseBody VS ResponseEntity
위에서 @ResponseBody 어노테이션과 ResonseEntity 클래스를 비교해보았습니다.
기본적으로 @ResponseBody는 HTTP body만 담아주는 역할을 하고
ResonseEntity는 더 세부적으로 HTTP body를 포함하여 header와 status를 설정해줄 수 있습니다.
@ResponseBody 말고 ResponseEntity를 사용해야하나?
포스팅 작성 중 ResponseEntity를 꼭 사용해야한다는 의견이 비교적 많이 있는 것 같았습니다.
그러나 개인적인 생각으로 ResponseEntity 사용은 필수가 아닌 것 같다는 생각이 들었습니다.
@ResponseBody는 규격화되지 않았고 ResponseEntity은 규격화 되어있다고 생각할 수 있겠으나
@ResponseBody 또한 일차적 목적은 HTTP 규격에 맞는 응답 생성이며 단지 HTTP header와 status 설정의 기능이 빠져있는 것으로 생각할 수 있겠습니다.
그러므로
1. @ResponseBody 도 규격화된 HTTP 응답이라는 점
2. ResponseEntity의 java doc에서 해당 클래스가 사용되는 특정 상황을 나열했다는 점
3. @ResponseStatus로 status 지정이 가능하다는 점
의 이유로 ResponseEntity가 모든 상황에서 강제되진 않고 header나 status의 설정이 빈번한 곳, 프로젝트에서 사용하면 좋지 않을까란 생각을 해보았습니다.
3. 커스텀 응답 클래스
커스텀 응답 클래스는 규격된 HTTP 응답 바디가 필요한 상황에서 사용됩니다.
위의 @ResponseBody와 ResponseEntity는 Http 프로토콜 자체에 대한 규격입니다.
하지만 백엔드와 프론트엔드가 원활히 소통하기 위해선 응답 바디에 대한 규격이 필요할 때가 있습니다.
보통 프로젝트 별로 팀 내에서 협의하여 커스텀 응답 클래스의 구조를 작성합니다.
@ResponseBody 나 ResponseEntity만을 이용한 것보다 보단 이처럼 커스텀 응답 클래스를 사용하여
응답 바디에 대한 규격까지 맞춰주는 것이 최근 REST API 개발 시 좀 더 주류인 듯합니다.
/**
* 프론트엔드와 통신하기 용이하게 규격화된 응답 객체가 있으면 좋다.
* 구조는 딱 정형화되어 있는 것은 아니니 협의하면 됨
*/
@Getter //getter 없으면 406에러 발생
public class ApiResponse<T> {
private int code;
private HttpStatus status;
private String message;
private T data;
public ApiResponse(HttpStatus status, String message, T data) {
this.code = status.value();
this.status = status;
this.message = message;
this.data = data;
}
public static <T> ApiResponse<T> of(HttpStatus status, String message, T data) {
return new ApiResponse<>(status, message, data);
}
public static <T> ApiResponse<T> of(HttpStatus status, T data) {
return ApiResponse.of(status, status.name(), data);
}
public static <T> ApiResponse<T> ok(T data) {
return ApiResponse.of(HttpStatus.OK, HttpStatus.OK.name(), data);
}
}
커스텀 응답 클래스도 ok() 혹은 failure() 같은팩토리 메서드를 통해서 생성되는 경우가 많으며
에러 핸들링 시 @ResponseBody에서와 마찬가지로 @ResponseStatus를 설정해주면 해당 status에 맞는 값으로 응답이 발생합니다.
- 추가사항 -
에러를 포함한 모든 응답에 ApiResponse라는 커스텀 응답 객체를 보내는 경우 이런 이슈가 발행하였습니다.
에러 클래스에서 status를 설정했음에도 불구하고 응답 객체에서 status를 @ResponseStatus로 설정했기 때문에 각각의 status를 별도로 지정하는 경우가 발생했습니다.
IdNotFouneException 예외 클래스에서 Status를 404로 주고 있지만 실제 httpStatus 를 BadRequest로 설정한 탓에 400 에러가 떨어지고 있습니다. 이러한 실수가 발생되지 않기 위해 Status 설정이 필요한 상황에선 ResponseEntity를 사용하여 하나의 값으로 통일하는 게 좋을 것 같습니다.
성공 시점엔 ApiResponse만을 반환하고 에서 시점엔 ResonseEntity<ApiResponse>를 반환해도 괜찮을까 ?
라는 의구심이 생길 수도 있을 것 같습니다. 정답은 괜찮다 입니다.
ApiResonse만 반환하는 것은 @ResponseBody로 규격화된 Http의 바디에 ApiReponse를 담은 것입니다.
ResponseEntity<ApiResponse> 또한 규격화된 Http 바디에 ApiResponse를 담은 것이므로
클라이언트 입장에서 동일한 바디 규격으로 응답을 받게 됩니다..! 다만 HttpStatus 등의 설정을 개발자가 추가 설정할 수 있는지에 대한 여부만 다른 것 뿐입니다.
참고)
https://ksh-coding.tistory.com/89
https://dev-splin.github.io/spring/Spring-ResponseEntity/
https://tecoble.techcourse.co.kr/post/2021-05-10-response-entity/
https://yeonyeon.tistory.com/257
'백엔드 > Spring' 카테고리의 다른 글
동기인듯 동기 아닌 동기 같은 너 - 비동기 블로킹 (Spring MVC에서 Flux 사용하기 by WebClient) (0) | 2024.12.24 |
---|---|
HTTP Method & Content-Type (0) | 2024.08.29 |
WAS 내부 요청 처리를 코드로 알아보자! - 1. EndPoint 분석 (0) | 2023.12.24 |
AOP 적용 방법 5가지 정리 (프록시를 이용한 트랜잭션 AOP) (0) | 2023.12.13 |
WAS(톰캣)의 내부 구성 및 작동 과정 - 1. 초기화 단계 (0) | 2023.12.10 |