프론트와 협업할 때 어떤 request를 받을 것이며, 어떤 response를 보낼 것인지 협의하는 것이 중요하다. response 형식을 어떻게 할지 정하기 나름이겠지만, 이번에는 API 요청에 성공해서 응답을 보내거나, 예외처리를 보낼 때 response 형식을 통일하려고 한다. 공통으로 사용할 ResponseDto를 구현해보자!
현재 진행하고 있는 프로젝트에서는 API 요청이 성공한 경우, 크게 두 가지의 응답이 나갈 수 있다.
1) 클라이언트가 요청한 데이터
2) 성공 상태코드와 메시지가 담긴 SuccessResponse
@Getter
public class SuccessResponse {
private int status;
private String message;
@Builder
private SuccessResponse(int status, String message) {
this.status = status;
this.message = message;
}
public static SuccessResponse of(HttpStatus status, String message) {
return SuccessResponse.builder()
.status(status.value())
.message(message)
.build();
}
}
@Getter
public class ErrorResponse {
private int status;
private String message;
@Builder
private ErrorResponse(int status, String message) {
this.status = status;
this.message = message;
}
public static ErrorResponse of(ErrorType errorType) {
return ErrorResponse.builder()
.status(errorType.getCode())
.message(errorType.getMessage())
.build();
}
public static ErrorResponse of(HttpStatus status, String message) {
return ErrorResponse.builder()
.status(status.value())
.message(message)
.build();
}
public static ErrorResponse of(BindingResult bindingResult) {
String message = "";
if (bindingResult.hasErrors()) {
message = bindingResult.getAllErrors().get(0).getDefaultMessage();
}
return ErrorResponse.of(HttpStatus.BAD_REQUEST, message);
}
}
@Getter
public class ApiResponseDto<T> {
private boolean success;
private T response;
private ErrorResponse error;
@Builder
private ApiResponseDto(boolean success, T response, ErrorResponse error) {
this.success = success;
this.response = response;
this.error = error;
}
}
ResponseUtils
class를 만들어 이 안에 static method를 만들 것이다.public class ResponseUtils {
// 요청 성공인 경우
public static <T> ApiResponseDto<T> ok(T response) {
return ApiResponseDto.<T>builder()
.success(true)
.response(response)
.build();
}
// 에러 발생한 경우
public static <T> ApiResponseDto<T> error(ErrorResponse response) {
return ApiResponseDto.<T>builder()
.success(false)
.error(response)
.build();
}
}
ApiResponseDto의 response 값으로 BoardResponeDto를 넣어 리턴한다.
public ApiResponseDto<BoardResponseDto> createPost(BoardRequestsDto requestsDto, User user) {
Board board = boardRepository.save(Board.of(requestsDto, user));
return ResponseUtils.ok(BoardResponseDto.from(board));
}
CustomExceptionHandler
에서 프로젝트 전역에서 발생하는 예외들을 처리해준다.
예외처리시, 응답 결과 status에 상태 값을 넣어주어야 하므로 ResponseEntity를 리턴하도록 구현했다.
ResponseEntity의 body 부분은 ApiResponseDto가 나타나도록 한다.
@Slf4j
@ControllerAdvice
public class CustomExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiResponseDto<ErrorResponse>> methodValidException(MethodArgumentNotValidException e) {
ErrorResponse response = ErrorResponse.of(e.getBindingResult());
log.error(response.getMessage());
return ResponseEntity.badRequest().body(ResponseUtils.error(response));
}
@ExceptionHandler(RestApiException.class)
public ResponseEntity<ApiResponseDto<ErrorResponse>> customException(RestApiException e) {
ErrorResponse response = ErrorResponse.of(e.getErrorType());
log.error(response.getMessage());
return ResponseEntity.badRequest().body(ResponseUtils.error(response));
}
}
성공 메시지, 에러 메시지, 요청에 대한 데이터 값 등 어떠한 응답을 보내더라도, response의 형식은 success, response, error를 담아 보내주고 있다. 어떠한 요청이든 공통된 response 형식으로 응답을 보내는 것이 반드시 해야할 정답은 아니겠지만, 프론트에서 response 데이터에서 필요한 값을 찾을 때 좀 더 편리하지 않을까 싶다. 어떤 요청이든 형식은 정해져있기 때문에 success가 false인 경우는 error 필드를 가져와 작업을 하고, success가 true인 경우는 response 필드를 가져와 작업을 하면 되니 말이다.