웬만하면 서버에서는 5xx 오류를 발생시키면 안된다.
ex) 사용자의 잔고가 부족한 경우, 스무살 이상 이용가능한 서비스에서 스무살 이하 사용자가 요청한 경우 등은 5xx 오류를 내면 안된다.
-> 오류는 서버에 문제가 있을 때 발생시켜야 한다. (쿼리문제, 널포인터익셉션 등..)
-> 위의 예시는 어떻게 보면 정상 요청인 것이다. (예외 케이스일 뿐, 서버 문제가 아님)
컨트롤러와 서비스 간의 예외처리에 대해서는 ControllerAdvice 찾아보기!
위의 예시 중에 스무살 이하 사용자가 요청한 경우 5xx 에러가 아닌 다른 에러를 출력해야 하는데, 400? 200?
-> 마침 내가 궁금했던 점을 어떤 분께서 질문을 올리셨는데, 인프런 김영한님의 답변이 이해가 잘 되어서 가져왔다.
여기서 먼저 5xx를 제외한 선택지가 400이나 200으로 처리해야 하는데요.
클라이언트와 서버가 서로 API 스펙을 맞출 때 만약 나이는 20살 이하로 넘기면 안됩니다. 라고 HTTP API 스펙에 정의를 했으면 400으로 응답을 반환하는 것이 맞습니다. 이것은 명확하게 클라이언트가 서로간의 약속을 지키지 않은 것이지요. 클라이언트는 검증 로직을 만들어서 20살 이하면 서버로 요청이 넘어가지 못하게 막아야 합니다.
그런데 실무에서 개발을 해보면 항상 이렇게 단순하게 문제가 끝나지는 않습니다.
클라이언트가 서버가 요구하는 스펙을 다 지켰을 때 발생하는 비즈니스 예외들을 어떻게 처리할까 하는 것이지요.
지금부터 말씀드리는 내용은 정답이라기 보다는 제가 개인적으로 선호하는 방식을 설명드리겠습니다.
예를 들어서 회원 가입을 신청할 때 연령과 관계없이 가입 신청을 받아야 하는데, 일반적인 경우는 바로 회원 가입이 되지만, 20살 이하인 경우는 내부 심사를 위해서 보류 상태로 반환해야 할 때, 어떻게 응답해야 하는가?
비슷한 시나리오로 보험금을 지급해 달라고 서버에 요청했는데 HTTP API 스펙의 요청은 모두 만족했지만, 비즈니스 로직상 이 사람이 요건을 모두 충족하지 못해서 지급이 승인 거절되는 경우에는 어떻게 응답해야 하는가?
이런 경우처럼 클라이언트의 요청에서 서로 약속한 HTTP API 스펙은 만족했지만, 그래서 비즈니스 로직까지 정상 수행했지만, 비즈니스 로직의 결과가 성공으로 처리되지 않을 수 있습니다. 이런 경우 400을 반환하게 되면, 클라이언트 개발자는 분명 서로 약속한 API 스펙을 다 지켰는데, 클라이언트 개발자에게 잘못 개발하셨어요. 라고 이야기하는 것과 같습니다.
그래서 이렇게 복잡한 비즈니스 로직이 들어가는 경우 200 OK로 응답하면서 결과에 내부 비즈니스 응답 코드를 정의해서 함께 전달합니다. 다음과 같이 응답 코드를 만들고 데이터도 한번 감싸서 전달하는 것이지요. 이것을 봉투 패턴(Envelope pattern)이라 합니다.
봉투가 포함된 응답
{
"code": "success" ("success", "fail", "hold", "deny" ...)
"data": {memberId: ... 결과 데이터}...
}
제가 선호하는 방식은 200 OK 라는 응답은 HTTP API 스펙상 요청이 성공해서 비즈니스 로직을 정상적으로 실행하는 단계까지 전달된 것으로 기준을 잡습니다. 결과적으로 HTTP 200 응답 코드는 비즈니스 로직의 내부 결과와는 무관하게 클라이언트와 서버간에 요청과 응답이 정상수행되었는지를 기준으로 잡습니다. HTTP 표준 스펙이 응답코드에 세상의 모든 비즈니스 로직까지 다 담을 수는 없으니까요.
물론 비즈니스 로직을 수행하다가 데이터베이스 접속이 안되거나 NullPointerException등이 발생하면, 이것은 500으로 응답을 하는 것이 맞습니다.
그런데 항상 이렇게 감싸면(이렇게 감싸는 것을 봉투 패턴(Envelope pattern) 이라 합니다.) 응답 구조가 너무 복잡해질 수 있습니다. 그래서 단순히 데이터만 조회하고, 크게 비즈니스 로직이 없는 경우에는 봉투 없이 단순하게 개발하는 것이 좋습니다.(특히 단순한 조회)
봉투 없는 단순한 응답
{
memberId: ...
username: ...
}
정리하면 다음과 같습니다.
1. 서로 약속한 HTTP API 스펙을 만족한다. -> 200, 만족하지 않는다. -> 400
2. 비즈니스 로직이 정상 수행되지만, 다양한 결과가 존재한다.(승인, 거절 등등) -> 200 + 비즈니스 코드 반환(봉투 패턴)
3. 비즈니스 로직을 수행하다가 내부에서 시스템 예외나 NullPointerException 등등 비즈니스와 관계없는 시스템 예외가 발생한다. -> 500
참고: 다양한 비즈니스 응답이 있는 복잡한 비즈니스 로직이 있는 HTTP API는 봉투 패턴을 고려하고, 비즈니스 로직이 거의 없는 단순한 조회에서는 봉투 패턴을 고려하지 않는다.
앞서 말씀드린 것 처럼 이것이 딱 정답이라기 보다는 제 경험에서 나온 부분이 많아서, 이것을 참고로 본인의 비즈니스에 맞는 HTTP API 스펙을 고민해보시면 좋겠습니다.
역시 강의 끝나고 다른 사람들이 질문한 것도 전부 읽어봐야 두 배로 공부되는 느낌!!! 💪