Skip to content

Koin Error Convention (2024)

Hana Kim edited this page Mar 26, 2024 · 2 revisions

Koin Error Convention

검증된 에러 컨벤션이 왜 필요한가

  • 현재 코인 서버에서 제공하는 에러 객체는 다음과 같습니다.
error {
   code: 100011,
   message: 'error message'
}
  • 에러 객체는 각각의 상태마다 다르게 응답되며, 클라이언트에서는 에러 분기 처리를 위한 추가적인 작업이 필요합니다.
  • 우리 팀은 이러한 문제를 해결하기 위해 코인 서버에서 내려오는 에러 객체를 통일시키고, 클라이언트에서는 이를 쉽게 처리할 수 있는 유틸리티 함수를 도입하기로 결정했습니다. 이를 통해 개발 생산성을 향상시키고 유지 보수성을 높일 수 있습니다.

클라이언트 에러 현황

  • 현재 코인에서는 axios 를 사용하여 api 요청을 진행하고 있기에, 들어오는 에러 객체도 axios 에러 형태에 종속되어 있습니다.
  • axios에서 정의한 에러 객체 인터페이스를 살펴보면 다음과 같습니다.
interface AxiosError<T = any, D = any> extends Error {
  config: AxiosRequestConfig<D>;
  code?: string;
  request?: any;
  response?: AxiosResponse<T, D>;
  isAxiosError: boolean;
  toJSON: () => object;
}
  • 서버에서 내려주는 코인 에러 객체는 response 값인 AxiosResponse에 들어가게 되며 실제로 이 값이 있는지 확인하는 로직이 필요합니다.
  • 또한, 에러 객체를 분기 처리하기 쉽게 변환해주는 과정도 필요할 것 입니다.

코인 에러 객체 정의

KOIN_WEB_RECODE 의 경우

  • APIClient에서 axios 요청 시, 오류가 발생하면 응답과 코인 에러 객체가 함께 있는지 확인하는 로직을 추가합니다.
private isAxiosErrorWithResponseData(error: AxiosError<KoinError>) {
    const { response } = error;
    return response?.status !== undefined
    && response.data.code !== undefined
    && response.data.message !== undefined;
  }
  • 만약 에러가 코인 에러 객체를 포함하고 있다면, 필요한 에러 속성만 재정의하여 변환합니다. 또한 코인 에러임을 명시하는 koin-error type을 지정합니다.
  • 코인 에러를 포함하지 않는다면, 기존 axios 에러를 axios-error type 과 함께 지정합니다.
  • 이렇게 정의된 오류 객체들을 처리할 수 있도록, 이를 throw 해줍니다.
// error 를 경우에 따라 KoinError와 AxiosError 로 반환합니다.
  private createKoinErrorFromAxiosError(error: AxiosError<KoinError>): KoinError | CustomAxiosError {
    //KoinError일 때
    if (this.isAxiosErrorWithResponseData(error)) {
      const koinError = error.response!;
      return {
        type: 'koin-error',
        status: koinError.status,
        code: koinError.data.code,
        message: koinError.data.message,
      };
    }
    //AxiosError일 때
    return {
      type: 'axios-error',
      ...error,
    };
  }

KOIN_OWNER_WEB의 경우

  • KOIN_OWNER_WEB은 오류 응답 처리를 담당하는 함수 isAxiosErrorWithResponseData는 동일하지만, 오류를 처리하는 방식에서 약간의 차이가 있습니다.
  • OWNER에서는 기본적으로 axios로 요청하는 모든 응답에 관해 client.interceptors.response 메서드를 활용하여 응답 처리를 진행하고 있습니다.
  • 따라서, 각각의 client interceptor 단에서 통일하여 에러 검증을 진행해주었습니다.
// createKoinErrorFromAxiosError 함수를 분리하여 axios 오류를 KOIN 오류로 변환
function createKoinErrorFromAxiosError(error: AxiosError<KoinError>): KoinError | CustomAxiosError {
  if (isAxiosErrorWithResponseData(error)) {
    const koinError = error.response!;
    return {
      type: 'koin-error',
      status: koinError.status,
      code: koinError.data.code,
      message: koinError.data.message,
    };
  }
  return {
    type: 'axios-error',
    ...error,
  };
}


//각각의 client에서 에러 검증 로직을 추가합니다.
client.interceptors.response.use(
  (response) => response,
  async (error) => { throw createKoinErrorFromAxiosError(error) },
);

accessClient.interceptors.response.use(
  (response) => response,
  async (error) => {
    ...
    throw createKoinErrorFromAxiosError(error);
  },
);

multipartClient.interceptors.response.use(
  (response) => response,
  async (error) => { throw createKoinErrorFromAxiosError(error) },
);

Koin 유틸 함수 정의

  • 클라이언트 측에서 코인 에러와 서버 에러를 구별할 수 없기 때문에, 유틸리티 함수 isKoinError를 정의하여 이를 확인합니다.
  • 에러는 기본적으로 unknown 으로 간주되기 때문에, error의 타입을 KoinError로 강제하고 type을 검사해줍니다.
  • 따라서, type이 koin-error 라면 true를 아니라면 false 를 반환합니다.
export function isKoinError(error: unknown): error is KoinError {
  try {
    //코인 서버 에러인지 아닌지를 확인
    return (error as KoinError).type === 'koin-error';
  } catch {
    return false;
  }
}

Usage Example

  • 클라이언트쪽으로 throw 된 에러는 다음과 같은 형태가 될 것 입니다.
image
  • 이렇게 검증된 에러의 사용 예시는 다음과 같습니다.
const useLogin = (state: IsAutoLogin) => {
...
    onError: (error) => {
      if (isKoinError(error)) {
        if (error.status === 401) showToast('error', error.message || '로그인 인증에 실패했습니다.');
        if (error.status === 403) showToast('error', error.message || '인증되지 않은 이메일입니다.');
        if (error.status === 422) showToast('error', error.message || '로그인 조건이 다릅니다.');
      }
      //클라이언트 에러 or 네트워크 에러 처리
      showToast('error', error.message || '서버 에러가 발생했습니다.');
    },
  });
  • 아직 에러 코드가 number 형태로 제공되어 구분이 불분명하기 때문에, 일단은 status를 기준으로 에러 분기 처리를 진행했습니다.
  • 일반적으로 스웨거에 명시된 에러 코드들을 모두 처리하는것을 권장드립니다.

기대효과

  • 서버와 클라이언트의 일관된 에러 처리로 생산성을 높일 수 있습니다.
  • 클라이언트에서 직접 변환한 에러 객체들로 유연한 에러 처리를 진행할 수 있습니다.
  • 에러를 분기마다 처리함으로 코인을 이용하는 사용자 경험을 높이고, 유지 보수에도 도움을 줄 수 있습니다.
  • 팀 컨벤션을 용이하게 지켜 일관성을 유지할 수 있을 것입니다.