안정적인 서비스를 위해서는 올바른 에러 처리가 필수입니다. DeliveryAPI의 에러 코드를 이해하고 적절히 대응하는 방법을 알아봅니다.

HTTP 상태 코드

에러 응답 형식

모든 에러는 { isSuccess: false, errorCode, error } 형태로 반환됩니다. errorCode로 에러 유형을 프로그래밍적으로 구분하세요.

{
  "isSuccess": false,
  "errorCode": "INVALID_PARAMS",
  "error": "유효하지 않은 송장번호입니다"
}

주요 errorCode

재시도 로직 구현

Exponential Backoff

async function retryRequest(fn, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      // 재시도 불가능한 에러
      if (error.response.status === 400 ||
          error.response.status === 401) {
        throw error;
      }

      // 마지막 시도면 에러 throw
      if (i === maxRetries - 1) {
        throw error;
      }

      // 대기 시간 계산 (1초, 2초, 4초)
      const delay = Math.pow(2, i) * 1000;
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

Rate Limit 처리

async function handleRateLimit(error) {
  if (error.response.status === 429) {
    // retryAfterSeconds는 HTTP 헤더가 아니라 응답 바디에 포함됩니다.
    const retryAfter = error.response.data?.retryAfterSeconds ?? 60;
    console.log(`Rate limit 초과 (${error.response.data?.errorCode}). ${retryAfter}초 후 재시도`);

    await new Promise(resolve =>
      setTimeout(resolve, retryAfter * 1000)
    );

    // 재시도
    return await retryRequest(apiCall);
  }
}

타임아웃 설정

const axios = require('axios');

const api = axios.create({
  baseURL: 'https://api.deliveryapi.co.kr/v1',
  timeout: 10000, // 10초
  headers: {
    'Authorization': `Bearer ${API_KEY}`
  }
});

api.interceptors.response.use(
  response => response,
  async error => {
    if (error.code === 'ECONNABORTED') {
      console.log('타임아웃 발생');
      // 재시도 로직
    }
    return Promise.reject(error);
  }
);
✅ 베스트 프랙티스
• 400/401 에러는 재시도하지 마세요
• 500/503 에러는 재시도하세요
• Exponential Backoff 사용
• Rate Limit 헤더 확인
• 모든 에러 로그 기록

에러 로깅

function logError(error, context) {
  console.error({
    timestamp: new Date().toISOString(),
    context: context,
    status: error.response?.status,
    errorCode: error.response?.data?.errorCode,
    message: error.response?.data?.error,
    stack: error.stack
  });
}

Circuit Breaker 패턴

연속된 에러 발생 시 API 호출을 일시 중단합니다.

class CircuitBreaker {
  constructor(threshold = 5, timeout = 60000) {
    this.failureCount = 0;
    this.threshold = threshold;
    this.timeout = timeout;
    this.state = 'CLOSED';
  }

  async call(fn) {
    if (this.state === 'OPEN') {
      throw new Error('Circuit breaker is OPEN');
    }

    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  onSuccess() {
    this.failureCount = 0;
  }

  onFailure() {
    this.failureCount++;
    if (this.failureCount >= this.threshold) {
      this.state = 'OPEN';
      setTimeout(() => {
        this.state = 'CLOSED';
        this.failureCount = 0;
      }, this.timeout);
    }
  }
}