API Response 포맷에 관한 고찰

개요

필자는 모바일 클라이언트를 위한 HTTP API를 개발하는 업무를 하고 있습니다. 종종 클라이언트 개발자들과 Request / Response에 대한 열띤(?) 토론을 진행하게 되는데요. 특히나 Response에 관해서는 끝이 없는 것 같습니다. 검색도 해보고 다른 개발자분들께 물어보아도 정답은 없는 것 같네요. 그래서인지 끝 없는 논쟁이 있나봅니다. 토론에서 더 큰 목소리를 내려면 레퍼런스가 필요하겠죠. 꼭 그런 이유 뿐만 아니라, 다른 분들은 어떤 고민들이 있고 어떤 이유로 설계를 결정하는지 궁금해서 내용을 조사, 정리하고 우리 회사의 HTTP Response 컨벤션을 만들어보려고 합니다.

현황

HTTP 표준 Status Code의 범주에 포함되는 응답들은 표준 응답을 사용합니다. 그리고 부가적으로 아래와 같은 형태의 포맷을 사용하고 있습니다.

// 예: 인증 실패로 인한 API 호출 실패
{
    "success": true, // API 호출 성공
    "data": [ // Payload: 성공 시 요청한 데이터
        {
            "cardNo": "777100293550015568",
            "cardStatusCode": "USE",
            "pauseReasonCode": null,
            "restPayPoint": 0
        }
    ]
}

// 예: 인증 실패로 인한 API 호출 실패
{
    "success": false, // API 호출 실패
    "data": { // Payload: 실패 시, 실패에 관한 상세 정보
        "status": 403, // Status Code와 동일한 값
        "name": "ForbiddenException", // 내부 Exception 이름
        "message": "auth failed with /v1/membership/coupon" // 메시지
    }
}

위의 응답 포맷은 몇 가지 개선 포인트를 가지고 있습니다.

  • 클라이언트는 HTTP Status Codesuccess 필드 중 어떤 것을 기준으로 상태를 결정해야 할지 모호합니다.
  • 실패 시, data.status 필드는 HTTP Status Code와 동일한 값으로 불필요 합니다.
  • data.name 필드는 서버 내 Exception의 이름으로 클라이언트에게는 필요하지 않은 정보 입니다.

바로 개선과 설계로 들어가기 전에 설계에 참고/검토한 내용들을 정리해보았습니다.

JSend

필자가 잡은 설계 방향과 가장 유사한 방식의 포맷입니다. error 상태도 명시적으로 표시한다는 것이 차이점입니다. 그리고 성공 시에도 data가 필수 필드이기 때문에 응답할 데이터가 없더라도 null로 응답해야합니다.

TypeDescriptionRequired KeysOptional Keys
successAll went well, and (usually) some data was returned.status, data
failThere was a problem with the data submitted, or some pre-condition of the API call wasn't satisfiedstatus, data
errorAn error occurred in processing the request, i.e. an exception was thrownstatus, messagecode, data
{
    status : "success",
    data : { "post" : { "id" : 2, "title" : "Another blog post", "body" : "More content" }}
}

{
    "status" : "fail",
    "data" : { "title" : "A title is required" }
}

{
    "status" : "error",
    "message" : "Unable to communicate with database"
}

카카오 API

  • 오류 시에만 별도 코드 사용함

카카오 REST API 레퍼런스에는 실패 시에 HTTP Status Code를 사용함과 동시에 내부적으로 사용되는 상태코드와 메시지를 전달하고 있습니다. 조금 특이한 점으로는 자체 상태코드의 값들은 음수값을 사용한다는 점입니다. 숫자 타입의 필드를 사용한다면, 오류 상태에 대한 코드값으로 음수를 사용하므로 조금 더 구분되는 효과가 있다는 장점이 있을 것 같습니다.

// 예: API의 허용된 요청 회수 초과
HTTP/1.1 400 Bad Request  
{
    "code": -10,
    "msg": "API limit has been exceeded."
}

REST API Response Body 형식에 대한 경험적 구조

  • 성공/오류 구분없이 동일한 형태 사용

HTTP Status Code를 사용하되, 부가적인 의미를 부여하기 위해서 추가 코드를 붙이고 - 예를 들어 Post 메소드 성공 시 201 + 00 + 0(HTTP Status Code + 자체 정의 타입코드 + 자체 정의 구분코드) - 성공/실패 여부에 관계없이 code, message, result라는 필드를 결과값에 포함하고 있습니다. 이렇게 설계하게 된 이유가 흥미로운데, HTTP Status Code는 브라우저를 위한 값이라는 것입니다. 거의 대부분의 HTTP(Rest) API는 특정한 클라이언트를 위한 것이며, 브라우저를 위한 응답값보다 더 구체적인 내용들이 필요하다는 것입니다. 비슷하게 생각해본 적이 있고 일리가 있는 것 같습니다.

// 예: 필수 데이터가 전달되지 않은 경우
HTTP/1.1 400 Bad Request
{
  "code": 412000,
  // 412 : 사전조건 실패
  // 0 : required param error
  // 00 : 이메일 - 임의로 00 은 이메일로 정한 것
  "message": "email Required in request body"
}

Is there any standard for JSON API response format?

설계 방향성에 대해 시작점에 질문해야할 내용입니다. 우선 아래 세 가지 중에 한 가지를 정해서 설계의 방향을 잡아야 합니다.

  1. HTTP 표준만을 사용할 것인가?
    • 표준을 사용하여 범용성을 가지지만, 부가정보가 부족함
  2. HTTP Status Code + Json Body?
    • 상태 코드를 사용하고, 부가정보를 Json 응답을 통해 제공함
  3. HTTP Status Code 미사용 + Json Body?
    • 상태 코드를 통일(200 등)하고, Json 응답을 통해서만 의미를 전달함

2번 혹은 3번을 선택해야하는 상황인데, 3번은 완전한 비표준이라는 점 때문에 선택하기가 꺼려집니다. 그리고 클라이언트 개발자들과도 논의가 필요합니다.

결론