우리가 일반적으로 api 요청을 한 후, 그에 대한 응답(response)로 받는 데이터는 크게 세가지로 나눌 수 있다.
단일 데이터
리스트 데이터
에러
이 때, 이를 아무 가공도 없이 엔티티 그대로 반환하게 되면 엔티티가 외부에 그대로 노출되어버린다는 문제점과 상황에 따른 에러들을 적절하게 처리할 수 없게 되어버린다.
문제점을 예시를 통해 직접 확인해보자.
현재 데이터 베이스에는 Book
테이블과, User
테이블이 존재하며 N:1 관계를 맺는다.
이 때, 다음과 같이 데이터 단건조회, 데이터 리스트조회 api를 각각 작성할 수 있다.
[ BookController 클래스 ]
[ BookService 클래스 ]책 단건조회 api를 호출할 경우 단일 엔티티인 Book을,
책 목록 조회 api를 호출할 경우 엔티티 리스트인 List<Book> 을 반환하도록 작성했다.
조회 결과를 확인해보자.
위와 같이 두 경우 모두 Status : 200 OK
의 정상 응답 결과를 반환받을 수 있다.
그렇다면 데이터베이스에 존재하지 않는 책을 조회하려고 하면 어떻게 될까?
존재하지 않는 id인 bookId = 3
으로 단건조회 api를 호출한 결과이다.
이 경우 위와 같이 Status:500, ServerError
, 서버상의 에러가 발생하는데,
이를 그대로 반환하는 것은 당연히 좋지 않은 방법이다.
대신, 해당 아이디를 가진 책은 존재하지 않는다는 응답을 돌려줘야 할 것이다.
따라서, 이는 서버단에서 직접 코드로 문제상황에 따라 에러를 내려주는 과정이 필요하다.
그렇다면, 우리가 정의할 응답에 포함되어야할 속성에는 무엇이 있을까?
다음과 같은 정보들이 응답에 포함되도록 코드를 수정해보자.
일단 response 라는 이름을 가진 패키지를 생성하자.
이제 아래 4개의 클래스들을 생성할 것이다.
CommonResponse - 공통 속성
SingleResponse<T> - 공통속성 + 엔티티 T의 단일 데이터
ListResponse<T> - 공통속성 + 엔티티 T의 리스트 데이터
단일 데이터와 리스트 데이터는 그 결과의 자료형에만 차이가 있다. (T 또는 List<T>)
따라서 CommonResponse클래스에 공통 속성( success, code, message ) 을 부여하고,
각각 CommonResponse를 상속해 데이터만 추가해주면 된다.
기존의 데이터는 T 또는 List<T> 자료형을 갖는다.
이제 얘네를 각각 우리가 만든 SingleResponse 또는 ListResponse 로 바꿔서 내보내주면 된다.
반환받은 데이터를 감싸서 Controller단에 넘겨줄 서비스 클래스를 생성해준다.
이 때 bean으로 등록되어야하므로, @Service
어노테이션을 추가한다.
getSingleResponse
getListResponse
이제, 기존의 BookController의 반환값을 수정하자.
ResponseService 객체를 생성자 주입한 후, 이를 이용해 기존 데이터를 감싸 반환하면 된다.
즉, 데이터베이스에서 받아온 데이터(BookService.XXX)들은 모두 Book 또는 List <Book> 자료형이다.
이를 Response객체로 감싸서 (ResponseService.XXX), 우리가 정의한 자체 클래스를 최종적으로 반환할 수 있다.
이제, 책 단건조회 api와 책 목록 조회 api를 다시 호출해보자.
예상과 달리 406,Not Acceptable 에러가 발생한다.. 당황
406 에러의 경우, 그 대상에 대한 표현이 불가한 경우 발생하는 에러로(rfc 참조), 주로 Json 오브젝트로 변환하는데에 문제가 생겨 발생한다.
BookController에서는 SingleResponse를 Json 객체로 변환하면서 GetXXX를 이용해 데이터를 가져와야하는데
해당 속성들이 private 필드로 선언되어 있어 불가능한 것이다.
따라서, CommonResponse, Single/ListResponse 클래스에 @Getter
어노테이션을 추가해준다.
다시 api를 호출해보자.
다음과 같이, 성공여부, 코드, 메세지와 함께, data 내부에 실제 원하던 값들이 함께 반환되는 것을 확인할 수 있다.
이렇게 수정할 경우, 이 api를 호출한 뷰 단에서도 success 여부를 검사하고, 그에 따라 data 를 꺼내 사용하기에 용이해진다.
이제 다음 포스트에서, 앞서 말했던 에러를 그대로 노출하지 않고, 상황에 따라 반환할 수 있도록 코드를 수정해보자.
큰 도움 됐습니다!