오늘은 API 응답 통일하는 코드를 알아보겠습니다!!
협력을 할 때, 프론트엔드 개발자는 응답이 통일 되어 있지 않으면, 응답마다 구조를 바꿔줘야해서 매우 힘들게 됩니다. 특히 성공인 경우와 실패인 경우 양식을 통일 시켜줘야 합니다.
응답 형식을 다음 그림처럼 맞춰줄겁니다

먼저 통일된 응답 클래스를 생성해주겠습니다.
@Getter
@AllArgsConstructor
@JsonPropertyOrder({"isSuccess", "code", "message", "result"})
public class ApiResponse<T> {
@JsonProperty("isSuccess")
private final Boolean isSuccess;
private final String code;
private final String message;
@JsonInclude(JsonInclude.Include.NON_NULL)
private T result;
// 성공한 경우 응답 생성
public static <T> ApiResponse<T> onSuccess(T result){
return new ApiResponse<>(true, SuccessStatus._OK.getCode() , SuccessStatus._OK.getMessage(), result);
}
public static <T> ApiResponse<T> of(BaseCode code, T result){
return new ApiResponse<>(true, code.getReasonHttpStatus().getCode() , code.getReasonHttpStatus().getMessage(), result);
}
// 실패한 경우 응답 생성
public static <T> ApiResponse<T> onFailure(String code, String message, T data){
return new ApiResponse<>(false, code, message, data);
}
}
onSuccess 함수에서는 정상적으로 작동했다는 ok 코드와 messege, result를 넣어서 APIResponse를 반환하고 있습니다.
만약에 ok 외에 다른 성공 코드를 넣고 싶다면 of를 이용해서 원하는code를 넣으면 됩니다.
그리고 result가 T인 경우는 result 타입이 어떤게 들어올지 모르기 때문에 설정해줬습니다.
onFailure의 경우에는 code와 message, data를 넣게 되는데, 이 부분에 대해서는 핸들러일 때 더 자세히 알게 됩니다.
그러면 SuccessStatus랑 Base code는 어떻게 되어 있을까요?
@Getter
@AllArgsConstructor
public enum SuccessStatus implements BaseCode {
// 일반적인 응답
_OK(HttpStatus.OK, "COMMON200", "성공입니다.");
// 멤버 관련 응답
// ~~~ 관련 응답
private final HttpStatus httpStatus;
private final String code;
private final String message;
@Override
public ReasonDTO getReason() {
return ReasonDTO.builder()
.message(message)
.code(code)
.isSuccess(true)
.build();
}
@Override
public ReasonDTO getReasonHttpStatus() {
return ReasonDTO.builder()
.message(message)
.code(code)
.isSuccess(true)
.httpStatus(httpStatus)
.build()
;
}
}
SuccessStatus는 BaseCode를 implement 합니다.
SuccessStatus는 enum 타입으로 필드를 설정하면
private final HttpStatus httpStatus;
private final String code;
private final String message;
다음과 같이 이렇게 enum을 생성할 수 있습니다.
_OK(HttpStatus.OK, "COMMON200", "성공입니다.");
아까 onSuccess 함수에서 불러온 것이 바로 이것입니다.
그리고 @Getter를 통해서 모든 필드의 값을 get 할 수 있게 되었습니다. 그래서 APIResponse에 code랑 message 값을 넣을 수 있는 것이죠
onSuccess를 실행하면
{
"isSuccess ": true,
"code" : "COMMON200",
"message" : "성공입니다.",
"result" :
{
무언가...
}
}
이렇게 응답 값이 나오게 됩니다.
그리고 getReasonHttpStatus는 ApiResponse의 of에서 사용하게 됩니다.
//APIResponse.java
public static <T> ApiResponse<T> of(BaseCode code, T result){
return new ApiResponse<>(true, code.getReasonHttpStatus().getCode() , code.getReasonHttpStatus().getMessage(), result);
}
APIResponse의 of 메소드를 보면 BaseCode의 getReasonHttpStatus를 호출하고 있습니다. getResonHttpStatus 함수는 SuccessStatus에서 구체화해주고 있죠.
이 함수의 내용은 빌더 패턴을 사용한 것인데, 한마디로 ReasonDTO라는 객체를 생성하고 그 객체에 message, code 등등 필드 값을 넣어준거라고 생각하시면 됩니다.
public interface BaseCode {
public ReasonDTO getReason();
public ReasonDTO getReasonHttpStatus();
}
BaseCode는 다음과 같습니다.
아까 successStatus에서 본 함수를 추상화하고 있습니다.
@Getter
@Builder
public class ReasonDTO {
private HttpStatus httpStatus;
private final boolean isSuccess;
private final String code;
private final String message;
public boolean getIsSuccess(){return isSuccess;}
}
resonDTO는 다음과 같습니다.
@Builder라는 어노테이션이 붙어있는데, 이것이 빌더 패턴을 사용한다는 뜻입니다. 이것이 있으면 ReasonDTO를 생성할 때 빌더 패턴을 사용해 생성할 수 있습니다.
여기까지 성공했을 때의 응답 통일 방식을 알아봤습니다.
그래서 이걸 어떻게 생성하면 되는가?
컨트롤러에서 다음과 같이 함수를 실행하면 됩니다.
//case 1: onSuccess
@GetMapping("/")
public ApiResponse<T> test(){
return ApiResponse.onSuccess();
}
//case 2: of
@GetMapping("/")
public ApiResponse<T> test(){
return ApiResponse.of(_OK);
}
result는 넣어도 되고 안넣어도 됩니다.

여기까지 성공일 경우의 응답 통일 방식을 알아보았습니다.
다음 포스팅에서 응답 실패일 때 쓰는 에러 핸들러와 방식을 알아보도록하겠습니다. 이건 조금 복잡합니다