-
Notifications
You must be signed in to change notification settings - Fork 1
Koin Error Convention (2024)
Hana Kim edited this page Mar 26, 2024
·
2 revisions
- 현재 코인 서버에서 제공하는 에러 객체는 다음과 같습니다.
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
에 들어가게 되며 실제로 이 값이 있는지 확인하는 로직이 필요합니다. - 또한, 에러 객체를 분기 처리하기 쉽게 변환해주는 과정도 필요할 것 입니다.
- 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은 오류 응답 처리를 담당하는 함수
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) },
);
- 클라이언트 측에서 코인 에러와 서버 에러를 구별할 수 없기 때문에, 유틸리티 함수
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;
}
}
- 클라이언트쪽으로 throw 된 에러는 다음과 같은 형태가 될 것 입니다.
- 이렇게 검증된 에러의 사용 예시는 다음과 같습니다.
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를 기준으로 에러 분기 처리를 진행했습니다.
- 일반적으로 스웨거에 명시된 에러 코드들을 모두 처리하는것을 권장드립니다.
- 서버와 클라이언트의 일관된 에러 처리로 생산성을 높일 수 있습니다.
- 클라이언트에서 직접 변환한 에러 객체들로 유연한 에러 처리를 진행할 수 있습니다.
- 에러를 분기마다 처리함으로 코인을 이용하는 사용자 경험을 높이고, 유지 보수에도 도움을 줄 수 있습니다.
- 팀 컨벤션을 용이하게 지켜 일관성을 유지할 수 있을 것입니다.