들어가기 앞서

 

API는 web FrontEnd, IOS, Android 등 어떠한 클라이언트도 이용할 수 있는 상황이다. 응답에 대해서 유연하고, 클라이언트에게 필요한 데이터를 유연하게 변경할 수 있어야 한다. 이런 조건을 만족시키는 공통 응답 형식을 만들어보자.

 

 

조건 살펴보기

 

응답 형식에는 다양한 조건이 있을 수 있다. 임의의 로 조건을 설정하고 필요한 이유에 대해서 알아보자.

 

 

1. HttpStatus가 포함

 

클라이언트의 보낸 요청에 대한 서버의 응답 코드이다. 이것을 통해서 클라이언트 측에서는 성공대인 200, 클라이언트 오류인 400대 등을 알 수 있으므로 포함돼야 한다.

 

2. 응답 메시지

 

HttpStatus만으로는 모든 상태를 설명할 수 없다. 그래서 메시지를 통해서 명확한 요청의 상태를 전달한다.

 

예를 들어 로그인 API에서 사용자가 응답 형식은 지켰지만, 비밀번호가 틀렸다고 가정해 보자. 

 

그렇다면 서버는 400대를 줘야할 것인가??

내 생각에는 그렇지 않다. 사용자는 응답 형식을 맞췄기 때문에 클라이언트 오류라고 볼 수 없다. 따라서 200의 HttpStatus를 보내야하 하는 게 적절하다고 생각한다. 하지만 실상은 비밀번호가 틀렸기 때문에 로그인은 할 수 없다.

 

위의 문제를 해결하기 위해서 응답 메시지가 필요한 것이다. 로그인 예시를 해결해보자.

 

로그인 실패 -> HttpStatus 200 and LOGIN_FAILED

로그인 성공 -> HttpStatus 200 and LOGIN_SUCCESS

 

로 같은 HttpStatus가 같은 200이지만, 로그인 성공 실패라는 의미를 담을 수 있게 된다. 

 

 

3. 데이터

 

당연하지만, 다양한 API 요청에 대한 데이터를 담을 수 있어야 한다. 

 

 

4. 응답 시간

 

항상 클라이언트의 시각을 항상 믿을 수는 없다. 클라이언트 측의 시각을 임의로 조작한다 던 지, 국내가 아닌 해외에 위치한 경우를 생각하자. 클라이언트의 시각을 활용하여 로직이 동작하면, 오류나 치팅으로 쓰일 수 있다. 따라서 서버에서 반환하는 시각을 반환해야 한다. 여기서 서버의 로컬 시간이 아닌 ZonedDateTime으로 보내줘야 국내가 아닌 곳에서도 사용할 수 있다.

 

구현

 

위의 조건들을 하나씩 해결해 보자. 베이스는 이미 Spring에 존재하는 ResponseEntity를 사용하자.

 

1. HttpStatus포함

 

ResponseEntity의 HttpStatus를 넣어서 반환하도록 한다. 그렇다면 공통으로 사용할 CommonApiResponse에서 ResponseEntity를 반환하면 된다. (필요한 어노테이션을 선언한다.)

 

@Getter
@AllArgsConstructor
@Builder
public class CommonApiResponse {

    private HttpStatus httpStatus;

    public static ResponseEntity<CommonApiResponse> toEntity(final HttpStatus httpStatus){
        return ResponseEntity.status(httpStatus).body(null);
    }

}

 

body부분은 아직 null로 제외시키고, static 메서드인 toEntity를 호출하면 CommonApiResponse와 HttpStatus를 넣은 ResponseEntity가 반환되므로 조건을 충족시킨다.

 

2. 응답 메시지

 

이제 메시지 필드를 추가한다. 

 

@Getter
@AllArgsConstructor
@Builder
public class CommonApiResponse {
    
    private final String message;

    public static ResponseEntity<CommonApiResponse> toEntity(final HttpStatus httpStatus){
        return ResponseEntity.status(httpStatus).body(null);
    }

}

 

message를 추가하는 것은 아래와 같이 builder를 사용한다. 

 

return CommonApiResponse.builder()
    .message("SUCCESS")
    .build()
    .toEntity(HttpStatus.OK);

 

메시지는 단순히 String으로 넘기는 것이 아닌 enum을 정의해서 사용하는 것이 앞으로 늘어날 메시지에 대해서 관리하기 쉬울 것이다. 

 

 

3. 데이터

 

데이터는 다양한 데이터를 받을 수 있도록 제네릭을 사용한다. 

 

@Getter
@AllArgsConstructor
@Builder
public class CommonApiResponse<T> {

    private final String message;

    private final T data;

    public static ResponseEntity<CommonApiResponse> toEntity(final HttpStatus httpStatus){
        return ResponseEntity.status(httpStatus).body(null);
    }

}

 

제네릭을 사용하므로, 다양한 객체의 반환을 담을 수 있게 된다. 마찬가지로 Builder를 사용해서 담는다. 

 

return CommonApiResponse.builder()
    .message("SUCCESS")
    .data(ExampleData)
    .build()
    .toEntity(HttpStatus.OK);

 

4. 응답 시간

 

응답 시간은 ZonedDateTime으로 보내면 된다. 필드를 추가하고, 자동으로 초기화하도록 해주면 된다.

 

@Getter
@AllArgsConstructor
@Builder
public class CommonApiResponse<T> {

    private final String message;
    
    private final T data;
    
    private final ZonedDateTime sendTime = ZonedDateTime.now(ZoneId.of("Asia/Seoul"));

    public static ResponseEntity<CommonApiResponse> toEntity(final HttpStatus httpStatus){
        return ResponseEntity.status(httpStatus).body(null);
    }

}

 

테스트

 

만들어봤으니 테스트를 해보자. 데이터는 간단히 String을 보내보자.

 

 

API 응답으로 원하는 형태가 오는 것을 알 수 있다. 

 

결론

 

반드시 이 형식과 예시로 든 조건이 정답은 아니다. 더 필요한 값이 있을 수 있고, 생략해도 되는 값들이 있을 것이다. 또한 HttpStatus와 Message를 필드로 가지는 enum을 선언해서 더 간단하게 만들 수도 있다. 결국 하나의 베이스 응답이라는 것은 클라이언트뿐만 아니라 API를 만드는 서버 개발자 또한 사용하기 편하게 제작해야 한다. 각자의 팀에서 사용하는 컨벤션에 맞게 최적화하는 것이 가장 좋다고 생각한다. 

'JAVA' 카테고리의 다른 글

Java Null 처리에 대한 여정  (0) 2023.10.16
Java 8 -> 11 변경점  (0) 2022.12.07
Java Garbage Collection  (0) 2022.05.30
이펙티브 자바  (0) 2022.04.22
JAVA Overloading & Overriding  (0) 2022.02.26