ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring API 공통 response 포맷 개발하기
    Spring 2022. 7. 10. 18:24

    기존 프로젝트 API 에서 response 되는 데이터를 살펴보니,

     

    response 가 내려가긴 하는데,

    성공인 경우와 실패의 경우가 각각 다른 포맷의 response 가 내려가고 있었다.

     

    서버는 데이터를 주긴 하지만 프론트 입장에서는 이렇게 개발하기 난감할 것이라는 생각이 들기도 하고,

     

    게다가 사용자 정의 Exception 처리도 없어, 공통 response 포맷을 개발하는 것이 필요하다고 생각하였다.

     

     

    다른 블로그 및 글들을 찾아본 뒤 다음 응답 클래스를 만들어보기로 했다.

     

    @Getter
    public class ApiResponse<T> {
    
        private final int code;
        private final String message;
        private final List<String> errorDetails;
        private final T data;
        private final String responseTime;
    
        private ApiResponse(int code, String message, List<String> errorDetails, T data) {
            this.code = code;
            this.message = message;
            this.errorDetails = errorDetails;
            this.data = data;
            this.responseTime = LocalDateTimeUtil.getLocalDateTimeNowString("yyyy-MM-dd hh:mm:ss");
        }
    
    }

     

    1. 응답 클래스 설명

     

    우선은 다른 기능은 없이 필요한 요소들만 우선 나열해보았다.

     

    클래스에 <T> 를 추가해서 Response마다 각기 다른 타입을 리턴해서 사용해야 되므로 해당 조건에 맞는 사용이 가능한 제네릭을 사용할 수 있도록 하였다.

     

    여기서 제네릭(Generic) 은 클래스 / 인터페이스 / 메서드 등의 타입을 파라미터로 사용할 수 있게 해주는 역할을 한다고 한다.

     

     

    private final int code;

     

    응답 코드를 나타내는 code

    예전엔 String 으로 성공인 경우 "0000" 이라든지 문자열 값으로 많이 하였는데,

    여기선

    HttpStatus.OK

    처럼 표준 HttpStatus 코드를 사용하기 위해 int 로 설정하였다.

    HttpStatus.OK 의 값은 200 이므로 

    프론트에서는 200 값을 성공으로 간주하게 된다.

    예를 들어) request 파라미터 오류의 경우는 HttpStatus.BAD_REQUEST,

    인증 오류의 경우는 HttpStatus.UNAUTHORIZED 등으로 사용이 가능하다.

     

    이 부분은 문자열이 좋은지, 해당 HttpStatus 코드값이 좋은지는 알 수 없다.

     

    각자의 회사 혹은 개인의 사정에 따라 더 좋은 경우에 맞춰서 하면 좋을것 같다.

     

    private final String message;

     

    사용자 정의 오류 메시지 혹은 런타임 오류 등에서 전달된 핵심 메시지를 나타낸다.


    private final List<String> errorDetails;

     

    위 message 에서 표현하기 힘든 자세한 에러 내용표시들을 나타낸다.

    예) e.getMessage(), e.printStackTrace() 등등 을 나타내고 싶은 경우


    private final T data;

     

    실질적인 본문에 해당하는 데이터

    각각의 응답 dto에 따라 내용이 달라진다.


    private final String responseTime;

     

    응답 시간

     

     

    2. 응답 클래스 사용

     

    우선적으로 많이 쓰는 응답이 데이터가 있는 경우

    수정, 삭제 등 데이터가 없는 경우 가 있다고 생각하였다.

    그 외 에러 인 경우

     public static <T> ApiResponse<T> success(T data) {
        return new ApiResponse<>(StatusCode.OK_CODE, null, null, data);
    }
    
    public static ApiResponse<?> successWithNoData() {
        return new ApiResponse<>(StatusCode.OK_CODE, null, null, null);
    }

     

    (1) 데이터가 있는 경우

    success 함수에서 응답객체와 입력받는 객체를 T로

    <T> ApiResponse<T> 이렇게 되도록 하였고, 

    T data 로 각각 다른 포맷도 받아서 처리가 되도록 하였다.

     

    ApiResponse.success(응답 dto) 이런식으로 사용하도록 하였다.

     

    (2) 데이터가 없는 경우

    successWithNoData 함수에서 

    따로 받는 객체가 없으므로 ApiResponse<?> 로 되도록 하였고,

     

    ApiResponse.successWithNoData() 라고만 호출하면 

    data는 null 로만 설정하도록 하였다.

     

    (3) 에러의 경우

     

    public static ApiResponse<?> error(int code, String message, List<String> errorDetails) {
    
        return new ApiResponse<>(code, message, errorDetails, null);
    }

    에러 종류에 따른 다른 code 

    등 위에서 설명했으므로 생략하고 

     

    ApiResponse.error(HttpStatus.BAD_REQUEST.value(), e.getMessage(), Arrays.asList(request.getRequestURI()))

     

    처럼 사용이 가능하다.

     

     

    3. 커스텀 에러

     

    @Getter
    public class CustomException extends RuntimeException {
    
        public final int code;
        public final String message;
        public final List<String> errorDetails;
    
        public CustomException(int code, String message, List<String> errorDetails) {
            this.code = code;
            this.message = message;
            this.errorDetails = errorDetails;
        }
    }

     

    커스텀 에러는 RuntimeException 을 상속해서 사용하도록 해서 

    throw new 로 사용할 수 있게끔 하였다.

     

    해당 클래스를 호출 했을 때 받는 핸들러를  다음과 같이 설정하였다.

     

    @Slf4j
    @RestControllerAdvice
    public class ApiExceptionHandler {
    
        @ExceptionHandler(CustomException.class)
        @ResponseBody
        public ApiResponse<?> handleCustomException(CustomException ce, WebRequest request) {
    		log.error("call handleCustomException()");
            return ApiResponse.error(ce.getCode(), ce.getMessage(), ce.getErrorDetails());
        }
    
        @ResponseStatus(HttpStatus.BAD_REQUEST)
        @ExceptionHandler(RuntimeException.class)
        public ApiResponse<?> handlerRuntimeException(RuntimeException e, HttpServletRequest request) {
    		log.error("call handlerRuntimeException()");
            return ApiResponse.error(HttpStatus.BAD_REQUEST.value(), e.getMessage(), Arrays.asList(request.getRequestURI()));
        }

     

    이렇게 하면 

    throw new CustomException(StatusCode.ERROR_WITHDRAWAL_USER_CODE,
                              StatusCode.ERROR_WITHDRAWAL_USER_MSG, null);

    호출 했을 때 'handleCustomException' 를 타서 동작하는 것을 확인 할 수 있었다.

     

     

    4. 위에서 설명한 호출 결과들

     

    (1) 응답 있는 성공의 경우

     

    {
        "code": 200,
        "message": null,
        "errorDetails": null,
        "data": {
            "id": 21,
            "email": "test@test.com5",
            "accessToken": "eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NTc0NTA1MjEsImVtYWlsIjoidGVzdEB0ZXN0LmNvbTUifQ.dvOVM5OyhyUj6ZXKjHmv-JAGCKHEI10w0K-OrQ173JA",
            "refreshToken": "eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NTc2MTYxMjF9.PZ1pqRCMgDtgC9EwW-e-f_7qt2TYFABffgP8H7PuRjE",
            "isSignUp": false,
            "nickname": "12수업업슨슨",
            "userGenderType": null,
            "birthDate": null,
            "city": "금천구"
        },
        "responseTime": "2022-07-10 08:55:21"
    }

     

    (2) 응답 없는 성공의 경우

     

    {
        "code": 200,
        "message": null,
        "errorDetails": null,
        "data": null,
        "responseTime": "2022-07-10 08:55:21"
    }

     

     

    (3) 인증 에러의 경우

     

    {
        "code": 401,
        "message": "올바르지 못한 인증입니다.",
        "errorDetails": [
            "Full authentication is required to access this resource"
        ],
        "responseTime": "2022-07-10 09:20:15"
    }

     

    (4) 커스텀 에러의 경우

     

    {
        "code": 8100,
        "message": "탈퇴한 유저입니다.",
        "errorDetails": null,
        "data": null,
        "responseTime": "2022-07-10 09:22:33"
    }

     

     

    참조: https://velog.io/@qotndus43/%EC%8A%A4%ED%94%84%EB%A7%81-API-%EA%B3%B5%ED%86%B5-%EC%9D%91%EB%8B%B5-%ED%8F%AC%EB%A7%B7-%EA%B0%9C%EB%B0%9C%ED%95%98%EA%B8%B0

    'Spring' 카테고리의 다른 글

    @Transactional 에 대한 고찰  (0) 2023.03.26
    JPA Hibernate Proxy  (1) 2022.09.13
Designed by Tistory.