[그림일기 서비스 보고 공부하기] Response 개선하기

오젼·2024년 9월 1일
0

저번에 Response 사용을 두고 고민을 했었다.
현재로써 결론 지은 것은

success인 경우 SuccessResponse를 따로 정의하지 않고 body에 응답 결과를 담기,
error인 경우 ErrorResponse를 따로 정의하여 ResponseEntity로 파싱하기이다.

이때 ErrorResponse에는 code, message, invalidParams(optional) 필드를 적기로 했다.

RFC-7807, ProblemDetail

원래는 error의 경우 spring에서 제공하는 ProblemDetail을 사용해보려 했었다.

ProblemDetail은 RFC-9457(7807)에 정리된 error response의 스펙을 따라 쉽게 구현할 수 있도록 정의된 클래스이다.

@RestControllerAdvice와 같이 쓰면 ProblemDetail로 return을 했을 때 spring이 자동으로 ResponseEntity로 파싱해서 반환해준다. (status도 ProblemDetail의 status로 자동으로 적용된다.)

https://zuplo.com/blog/2023/04/11/the-power-of-problem-details
https://datatracker.ietf.org/doc/html/rfc7807
https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-ann-rest-exceptions.html

결국 사용하지 않았다.

ProblemDetail을 사용하면 type, title, status, detail, instance property가 필수로 포함 되어 온다.

이전에 고민했던 내용이 불필요한 필드를 최대한 빼는 것이 좋지 않겠냐는 거였다. 그래서 에러 코드, 설명만 담는 ProblemDetail을 만들고 싶었는데

https://mangkyu.tistory.com/204
https://mangkyu.tistory.com/205

해당 블로그에 나온대로 ErrorResponse 클래스를 만드는 게 나아보였다.

https://stir.tistory.com/343#google_vignette
-> 이 블로그를 보고 반환 타입을 ResponseEntity<Object>가 아니라 ResponseEntity<?> 로 하려고 했는데 ResponseEntity<Object> 를 쓴 이유가 있었다..
ResponseEntityExceptionHandler를 extends 했기 때문이다.
ResponseEntityExceptionHandler에는 예외처리 반환 타입들이 ResponseEntity<Object>로 되어있다.

결정된 ErrorCode, ErrorResponse

@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public interface ErrorCode {

    String getCode();

    String getMessage();

    @JsonIgnore
    HttpStatus getStatus();
}
public record ErrorResponse(
        @JsonUnwrapped
        ErrorCode errorCode,

        @JsonInclude(Include.NON_EMPTY)
        List<InvalidParam> invalidParams
) {

    public static ErrorResponse from(ErrorCode errorCode) {
        return new ErrorResponse(errorCode, null);
    }

    public static ErrorResponse of(ErrorCode errorCode, List<InvalidParam> invalidParams) {
        return new ErrorResponse(errorCode, invalidParams);
    }

    public ResponseEntity<Object> toResponseEntity() {
        return ResponseEntity.status(errorCode.getStatus()).body(this);
    }

    public void writeTo(HttpServletResponse response) throws IOException {
        response.setStatus(errorCode.getStatus().value());
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding("UTF-8");
        response.getWriter().write(JsonUtils.toJson(this));
    }

    public record InvalidParam(String name, String reason) {

        public static InvalidParam from(FieldError fieldError) {
            return new InvalidParam(fieldError.getField(), fieldError.getDefaultMessage());
        }
    }
}

정적팩토리 메서드를 구현하고 toResponseEntity 메서드도 구현했다.
spring security에서 error response를 만들 때 쓸 writeTo 메서드도 구현했다.

또 @JsonIgnore을 사용해 enum 객체가 직렬화될 때 status 필드는 포함되지 않도록 했고
@JsonUnwrapped를 사용해서 enum 객체가 직렬화될 때 중첩 형태로 변환되는 걸 막았다.

이걸 얼마나 고민했는지..........

그래도 RFC에서 제안된 error response 형식에 대해서도 알아봤고
spring에서 구현된 ProblemDetail에 대해서도 짚고 넘어갈 수 있었다.

그래도 앞으로는 이것보단 빨리 속도를 내고 2차, 3차 리팩토링을 하는식으로 해야겠다...

0개의 댓글