```js

       HTTP     |                 |

<---  Status    |  Response.code  | Response.data.code

       Code     |                 |


{

    httpStatusCode: ?,   --> httpStatusCode는 해당 서버로부터 오는 모든 api 응답의 일관된 처리를 위한 error code로서 의미를 가짐

    body: {

        code: ?,         --> body.code는 해당 서버로부터 오는 모든 api 응답의 일관된 처리를 위한 error code로서 의미를 가짐

        data: {

            code: ?      --> body.data.code는 주로 Enum code. 특정 api 내 범위의 error, 상태 등

        }

    }

}

// !!body.code 가 httpStatusCode로 모두 커버가 된다면 통합하여 1depth 줄일 수 있음!!

```

```js

@FE에서 받는 다면

 

http.get(`/api-1/example`)

    .then(resultCodeHandler(   --> body.code 에러 처리

        data => {

            data.code = ...    --> body.data.code 분기 처리

        },

        this.failUrl))

    .catch(() => {});          --> httpStatusCode 에러코드 처리

 

http.post(`/api-2/bar`)

    .then(resultCodeHandler(   --> api는 다르지만, body.code 에러 처리는 일원화하여 처리한다.

    ...

```

```java

Response<T> 타입

{

    "code" : {CommonCode},    // body.code가 된다.

    "message" : {String},

    "data" : T

}

```

```java

예제) Response<FooResult> 타입

{

    "code" : {CommonCode},    // 서버 전역적으로 사용되는 공통 응답 코드.

    "message" : {String},

    "data" : {                // 해당 API specific한 데이터

        "code": {FooEnum},

        "field1": bla,

        "field2": bla,

    }

}

```

  • HTTP API 응답은 위와 같이 나갈텐데, 응답 말고 메서드 사이에서 반환하는 리턴값을, `` FooResult``처럼 code와 data로 처리하고 싶은 경우가 있다.
  • 이 때 '매번 `` FooResult`` 같은 데이터 클래스를 만들어야 하는가?' 에 대해서, 이를 커버하는 `` Result<T,R>`` 타입을 만들고 싶다면.

```java

Result<T, R> 타입 // 보편적으로 많이 쓰이기 때문에 정의한 것이지 없어도 되는 타입. 각자 FooResult 만들어도 된다.

{

    "code" : T

    "message" : {String}

    "data" : R

}

```

```java

Response<Result<T, R>> 이면

{

    "code" : {CommonCode},    // 서버 전역적으로 사용되는 공통 응답 코드

    "message" : {String},

    "data" : {                // 해당 API specific한 데이터

        "code" : T,

        "message": {String},

        "result" : R

    }

}

 

// 상기했듯 httpStatusCode로 커버가 된다면 Response 타입이 차지하는 layer를 생략 가능함.

// (Response.code -> HTTP status 로 표현 가능 하다면)

```

 

참고로 Rust에서는 Exception이 없고, 대신 결과를 Result<T, E> 로 제어한다. 참고 할만한 컨셉.
이와 비슷하게 Kotlin의 runCatching, rx의 try 모나드가 있다.

 

 

가능하다면 HTTP status code를 이용해서 Response<T> layer를 없애는 것이 좋다.

  1. `` Response<T>`` 을 반환하게 되면, 받는 쪽에서도 보통 제네릭으로 받아야 하므로 ParameterizedTypeReference를 사용해야 한다.
  2. 보통은 애초에 실패에 대한 정보를 그렇게 계층적으로 구조화 할 필요가 없어서 불필요한 체크가 늘어난다.

 

```java

ResponseEntity<Response<MyResponse>> responseEntity = ...

 

if (!responseEntity.getStatusCode().is2xxSuccessful()

    || !ResultStatus.OK.getCode().equals(responseEntity.getBody().getCode())

    || responseEntity.getBody().getData().getAuthLevel() != AuthLevel.ONE) {

    new FooException("오류");

}

```

예제를 보면 체크를 3번이나 수행했다.

  1. 외부 API 응답 자체가 정상인지 (200 OK)
  2. 외부 API의 Response.code 가 정상인지
  3. 외부 API의 Response.data.code(authLevel) 이 정상인지

 

  • 물론 어디서 에러가 발생했는지를 구분해서 처리가 달라야 한다면 위와 같이 3번 체크하는 것이 의미가 있을 수 있다.
  • 하지만 이렇게 3단계로 계층화 해서 처리해야 하는 경우는 잘 없음. 보통 내가 관심 있는 것은 "외부 API가 결과 data를 줬냐 안줬냐" 이다. (1,2번 묶음)
    • 보통 그냥 외부 API에서 응답 제대로 주지 않는 [1, 2] 상황은 굳이 구분 할 필요가 없어서 묶어서 체크하는게 낫다.
    • => REST api  
  • [2, 3]을 묶어서 처리하는 것은 당연히 불가능하다. authLevel은 꼭 봐야 하니까.
  • *** 이렇게 여러 체크 중 어디서 실패하든 종단 처리로 가야하는 경우, Optional.filter 사용하는게 더 깔끔할 수도 있다.

 

body.data.code가 data code가 아닌 응답 코드의 의미를 지닐 때도, body.code(혹은 HTTP status)와 body.data.code를 분리하는 것이 좋은가?

예를 들어 bin 조회 같은 경우, 응답 케이스를 다음과 같이 세분화 할 수 있는데

1. 성공 및 결과 반환

2. 성공이나 해당 bin 찾을 수 없음 (NOT FOUND)

3. 성공이나 미지원 카드사

4. 실패로 입력값 에러 (PARAM ERROR)

5. 외부 에러 (EXTERNAL ERROR)

 

클라이언트 측에서 1~5 응답에 따른 분기 처리가 필요한 상황이라고 가정해보자. (즉, 1~5 응답은 반드시 클라이언트에서 구분되어야 한다.)

 

  1. 같은 통합 응답 코드(e.g., HTTP status 500)를 사용하고 message를 달리 쓴다.
    • 이 방법은 너무 fragile 하다.
  2. 1~5를 모두 통합 코드에 넣는다.
    • 해당 API에서만 발생하는 오류(특히 3번)임에도 통합 코드에 넣는 것이 적절하지 않아 보인다.
    • 이런 식으로 각 API에서 모두 통합 코드에 추가하다 보면 통합 코드 표가 너무 비대해진다.

 

그래서 필요한 경우, [공통 응답 코드:body.code]와 [세부 응답 코드:body.data.code]를 모두 사용한다.

세부 응답 코드는 API에 종속적인 코드이므로 변경이 있을 때 해당 API 이용자 에게만 고지하면 된다는 장점과

코드를 2개 이상 반환할 수 있다는 장점 등이 있다.

 

중요 ) 응답 코드를 구성할 때는 클라이언트의 관심사가 무엇이냐를 생각해야 한다.

외부에서 받은 응답 코드를 그대로 내려주는 것이 항상 좋은 방법은 아닐 수 있음.

클라이언트가 필요로 하지 않는 정보라면 응답 코드를 적당히 추상화해서 내려주는 것도 필요하다.

즉, 공통 코드 + 세부 코드로 내려줄 수 있는 상황이지만, 공통 코드로 merge 해서 내려주는 것.

 

이를 판단하는 좋은 기준은, 어떤 클라이언트를 사용하더라도(클라이언트가 n개가 되더라도) 모두 같은 처리를 할 것 같은가?를 생각해보는 것이다.

 

공통 응답 코드를 사용할 때는, 해당 공통 응답 코드를 깊은 layer에서 반환 할 가능성에 대해서도 생각해야 한다.

그래서 공통 응답 코드는 반드시 의미와 역할이 명확해야 한다. 의미가 명확한 공통 응답 코드라면, 어느 layer에서 반환하든 같은 의미를 지닐 것이므로 클라이언트에서 받았을 때 처리가 달라지지 않아 문제를 예방할 수 있다.

 

기타

서버에서 제공하는 api 이더라도 각 api 마다 응답 코드도 다르고 의미도 다르다면, 사용하는 쪽에서 일관된 처리 인터페이스를 작성할 수 없다.

그래서 상기한 응답 구조는 해당 서버로부터 오는 모든 api 응답에 대해 일관된 처리를 공유할 수 있도록 일종의 공통 응답 코드(CommonResponseCode)가 존재해야 함을 전제하고 있다.

 

  공통 응답 코드가 있는 경우 개별 응답 코드만 사용하는 경우
응답 코드 Enum 대부분의 응답은 공통 응답 코드 Enum을 참조하여 해결할 수 있다. 각 API 마다 모두 각자의 응답 코드 Enum을 정의해야 한다.
클라이언트 처리 클라이언트 코드에서 공통 처리부 CommonHandler를 정의할 수 있다. 공통 처리부를 정의할 수 없다. 그러나 어차피 같은 공통 응답 코드(e.g., 404)반환하더라도 API 마다 그 응답의 의미가 조금씩 다르기 때문에 공통 처리부는 크게 의미가 없을 수 있다.
     
     

이러한 장단점을 생각해 보면 공통 응답 코드가 존재하는 것이 관리하기 더 나은 경우가 많았다.