이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.
- Controller 반환값은 어떤 기준으로 view name 또는 response body가 되는가?
@ResponseBody,@RestController,ResponseEntity는 각각 무엇을 해결하는가?- API 응답 DTO와 HTTP status는 왜 함께 설계해야 하는가?
개요
Spring MVC는 handler method 반환값을 ReturnValueHandler로 해석한다. 반환값은 view rendering으로 갈 수도 있고 response body 작성으로 갈 수도 있다.
@ResponseBody는 반환 객체를 HTTP response body에 쓰겠다는 의미다. @RestController는 모든 handler method에 @ResponseBody 의미를 적용한다. ResponseEntity는 body, status, header를 함께 표현한다.
@ResponseBody와 @RestController
@Controller
public class MemberController {
@ResponseBody
@GetMapping("/api/members/{id}")
public MemberResponse member(@PathVariable Long id) {
return memberService.find(id);
}
}@ResponseBody가 있으면 반환 객체는 ViewResolver로 가지 않고 HttpMessageConverter를 통해 response body로 쓰인다. 이때 선택되는 converter는 반환 타입, Accept header, produces 조건, 등록된 converter에 따라 달라진다.
@RestController
public class MemberApiController {
}@RestController는 @Controller와 @ResponseBody를 합친 의미이므로 REST API Controller에 적합하다.
ResponseEntity
ResponseEntity는 status와 header를 명확히 제어할 때 유용하다.
@PostMapping("/api/members")
public ResponseEntity<MemberResponse> create(@RequestBody CreateMemberRequest request) {
MemberResponse response = memberService.create(request);
return ResponseEntity.status(HttpStatus.CREATED).body(response);
}생성 성공은 201 Created, 삭제 성공은 204 No Content, 조회 성공은 200 OK처럼 HTTP 의미를 응답에 반영할 수 있다.
body가 없는 응답은 ResponseEntity<Void>나 noContent()처럼 의도를 드러내는 방식이 좋다. 204 응답에 body를 억지로 넣으면 HTTP 의미와 클라이언트 기대가 어긋날 수 있다.
응답 DTO
응답 DTO는 외부로 공개하는 계약이다. Entity를 그대로 반환하면 다음 문제가 생길 수 있다.
- 내부 필드 노출
- lazy loading 중 예외
- 양방향 연관관계 순환 참조
- API 스펙과 도메인 모델의 결합
- 필드명 변경이 외부 API 변경으로 번짐
응답 DTO를 두면 API 스펙을 의도적으로 관리할 수 있다.
실전 팁
- API Controller는
@RestController, 화면 Controller는@Controller로 분리하는 편이 혼동이 적다. - 응답 status는 성공/실패 의미를 담는 설계 요소다. 전부 200으로 감싸지 않는다.
ResponseEntity는 header나 status가 중요할 때 쓰고, 단순 조회는 DTO 직접 반환도 충분하다.@ResponseBody라고 해서 항상 JSON은 아니다. 문자열, byte array, stream, problem response 등 반환 타입과 media type에 맞는 converter가 사용된다.- 에러 응답도 DTO로 설계한다. message만 던지면 클라이언트가 안정적으로 분기하기 어렵다.
- 응답 DTO에 필요한 필드만 담아 내부 모델 노출을 줄인다.
위험 신호!
@RestController에서 view name 문자열을 반환하려고 한다.@Controller에서@ResponseBody없이 JSON이 나갈 것이라고 기대한다.- Entity를 직접 반환해 lazy loading과 순환 참조 문제가 생긴다.
- 모든 성공과 실패를 HTTP 200으로 감싼다.
- 204 No Content 응답에 body를 함께 보내려 한다.
- 에러 응답 구조가 API마다 다르다.
확인 질문
확인 질문
@ResponseBody는 반환값을 어디로 보내는가?
- ViewResolver가 아니라 HttpMessageConverter를 통해 HTTP response body로 보낸다.
ResponseEntity는 무엇을 표현할 수 있는가?
- HTTP status, header, body를 함께 표현할 수 있다.
- 응답 DTO를 따로 두는 이유는 무엇인가?
- 외부 API 계약을 내부 Entity 구조와 분리하고 노출 범위를 통제하기 위해서다.