TIL - 20260701

juni·2026년 7월 1일

TIL

목록 보기
390/468

0701 백엔드 실무 심화 (8/N): 외부 API 연동과 실패 처리 설계


✅ 1. 외부 API 연동이란 무엇인가?

  • 외부 API 연동은 우리 서비스가 다른 회사나 외부 시스템의 API를 호출해서 기능을 처리하는 구조입니다.
  • 백엔드 실무에서는 문자 발송, 카카오 알림톡, 본인인증, 결제, 주소 검색, 광고 전환 API, 통신사 정책 API, CRM 연동처럼 외부 API를 사용하는 경우가 많습니다.
우리 백엔드 서버
  ↓
외부 API 호출
  ↓
외부 서비스 처리
  ↓
응답 수신
  ↓
우리 DB에 결과 저장

➕ 1-1. 외부 API 예시

구분예시
문자/알림SMS, LMS, 카카오 알림톡
인증본인인증, 휴대폰 인증, 이메일 인증
결제카드 결제, 가상계좌, 환불
광고네이버/구글/메타 전환 API
파일/검색주소 검색, 지도 API, OCR
업무 연동CRM, 상담 시스템, ERP, 통신사 API
  • 외부 API는 우리 서비스 바깥의 시스템이기 때문에 언제든 느려지거나 실패할 수 있습니다.
  • 그래서 외부 API 연동은 “성공했을 때”보다 실패했을 때 어떻게 처리할지가 더 중요합니다.

✅ 2. 외부 API 연동이 어려운 이유

➕ 2-1. 우리 마음대로 제어할 수 없다

  • 외부 API 서버가 느려질 수 있습니다.
  • 외부 API가 점검 중일 수 있습니다.
  • 인증키가 만료될 수 있습니다.
  • 요청 제한에 걸릴 수 있습니다.
  • 문서와 실제 응답이 다를 수 있습니다.
우리 서버는 정상
  ↓
외부 알림톡 API 장애
  ↓
상담 신청 API까지 느려지거나 실패할 수 있음

➕ 2-2. 실패 유형이 다양하다

실패 유형설명
Timeout응답 시간이 너무 오래 걸림
400 오류요청 형식이 잘못됨
401/403 오류인증키 오류, 권한 없음
429 오류요청 횟수 제한
500 오류외부 서버 내부 오류
Network Error네트워크 연결 실패
응답 포맷 오류예상과 다른 응답 구조
부분 성공일부 대상만 성공, 일부 실패
  • 외부 API는 단순히 성공/실패만 보는 것이 아니라 실패 원인별로 대응이 달라야 합니다.

✅ 3. 외부 API 연동 기본 흐름

➕ 3-1. 동기 호출 흐름

사용자 요청
  ↓
우리 서버 DB 저장
  ↓
외부 API 호출
  ↓
외부 API 응답 대기
  ↓
결과 DB 저장
  ↓
사용자 응답
  • 결제 승인, 본인인증 확인처럼 즉시 결과가 필요한 작업에 사용합니다.
  • 단점은 외부 API가 느리면 사용자 응답도 느려진다는 점입니다.

➕ 3-2. 비동기 호출 흐름

사용자 요청
  ↓
우리 서버 DB 저장
  ↓
Queue에 외부 API 작업 등록
  ↓
사용자에게 빠르게 응답

Worker
  ↓
Queue에서 작업 처리
  ↓
외부 API 호출
  ↓
결과 DB 저장
  • 알림톡, SMS, 이메일, 광고 전환 API처럼 즉시 결과가 필요하지 않은 작업에 적합합니다.
  • 실패 시 재시도와 이력 관리가 가능합니다.

✅ 4. 동기 처리와 비동기 처리 기준

  • 외부 API를 무조건 Queue로 빼는 것이 정답은 아닙니다.
  • 사용자가 즉시 결과를 알아야 하는 작업은 동기 처리가 필요합니다.
  • 사용자의 핵심 흐름을 막지 않아도 되는 작업은 비동기 처리가 좋습니다.

➕ 4-1. 동기 처리가 적합한 작업

작업이유
로그인즉시 성공/실패 필요
본인인증 확인인증 결과가 있어야 다음 단계 진행
결제 승인결제 성공 여부가 주문 상태에 직접 영향
쿠폰 사용 검증주문 금액 계산에 즉시 필요
중복 신청 검증저장 전에 결과 필요

➕ 4-2. 비동기 처리가 적합한 작업

작업이유
신청 완료 알림톡실패해도 신청 저장은 유지 가능
관리자 알림사용자 응답과 분리 가능
광고 전환 API후속 분석용
이메일 발송재시도 가능
대량 문자 발송시간이 오래 걸림
CRM 동기화나중에 재처리 가능
핵심 데이터 저장:
동기 처리

부가 알림/전송/연동:
비동기 Queue 처리

✅ 5. Timeout 설정

  • 외부 API를 호출할 때 timeout을 반드시 설정해야 합니다.
  • timeout이 없으면 외부 API가 응답하지 않을 때 우리 서버 요청도 오래 묶일 수 있습니다.

➕ 5-1. Timeout이 없는 문제

외부 API 응답 없음
  ↓
우리 서버가 계속 기다림
  ↓
API 응답 지연
  ↓
서버 자원 낭비
  ↓
다른 요청까지 느려짐

➕ 5-2. Timeout 기준 예시

API 종류Timeout 예시
인증 확인3초 ~ 5초
결제 승인5초 ~ 10초
알림톡/SMS3초 ~ 5초
광고 전환 API2초 ~ 3초
대량 파일 연동별도 비동기 처리
  • timeout은 외부 API 문서와 실제 응답 속도를 보고 조정해야 합니다.
  • 너무 짧으면 정상 요청도 실패할 수 있고, 너무 길면 서버 자원을 오래 잡아먹습니다.

✅ 6. Axios 기반 외부 API Client 설계

  • 외부 API 호출 코드를 Service 곳곳에 흩뿌리면 관리가 어려워집니다.
  • 외부 API별로 Client 클래스를 만들어서 요청, 응답, 에러 처리를 한곳에 모으는 것이 좋습니다.

➕ 6-1. 좋지 않은 방식

async createConsult(dto: CreateConsultDto) {
  const consult = await this.prisma.consult.create({ data: dto });

  await axios.post('https://sms-provider.com/send', {
    phone: dto.phone,
    message: '신청 완료',
  });

  return consult;
}
  • 외부 API 주소, 인증키, 에러 처리, timeout이 비즈니스 로직 안에 섞입니다.
  • 나중에 API 제공업체가 바뀌면 여러 코드를 수정해야 합니다.

➕ 6-2. 좋은 방식

@Injectable()
export class SmsClient {
  private readonly client: AxiosInstance;

  constructor(private readonly configService: ConfigService) {
    this.client = axios.create({
      baseURL: this.configService.getOrThrow('SMS_API_BASE_URL'),
      timeout: 5000,
      headers: {
        Authorization: `Bearer ${this.configService.getOrThrow('SMS_API_KEY')}`,
      },
    });
  }

  async sendSms(params: {
    phone: string;
    message: string;
  }) {
    const response = await this.client.post('/messages', {
      to: params.phone,
      message: params.message,
    });

    return response.data;
  }
}
  • 외부 API 설정을 Client에 모아두면 유지보수가 쉬워집니다.
  • Service는 smsClient.sendSms()처럼 의도를 중심으로 사용할 수 있습니다.

✅ 7. 외부 API 응답 표준화

  • 외부 API마다 응답 형식이 다릅니다.
  • 우리 서비스 내부에서는 외부 응답을 그대로 쓰기보다 내부 표준 형태로 변환하는 것이 좋습니다.

➕ 7-1. 외부 응답 예시

{
  "result_code": "0000",
  "result_message": "success",
  "message_id": "abc123"
}

➕ 7-2. 내부 표준 응답 예시

export interface SendMessageResult {
  success: boolean;
  providerMessageId?: string;
  errorCode?: string;
  errorMessage?: string;
}

➕ 7-3. 변환 예시

async sendSms(params: SendSmsParams): Promise<SendMessageResult> {
  try {
    const response = await this.client.post('/messages', {
      to: params.phone,
      message: params.message,
    });

    return {
      success: response.data.result_code === '0000',
      providerMessageId: response.data.message_id,
      errorCode: response.data.result_code,
      errorMessage: response.data.result_message,
    };
  } catch (error) {
    return {
      success: false,
      errorCode: 'SMS_REQUEST_FAILED',
      errorMessage: '문자 발송 요청에 실패했습니다.',
    };
  }
}
  • 외부 API 응답 구조가 바뀌어도 내부 Service 영향 범위를 줄일 수 있습니다.
  • 여러 문자 업체를 바꿔도 내부 응답 구조는 유지할 수 있습니다.

✅ 8. 에러 처리 기준

  • 외부 API 에러는 사용자 메시지와 개발자 로그를 분리해야 합니다.
  • 사용자에게 외부 API의 내부 에러 메시지를 그대로 보여주면 안 됩니다.

➕ 8-1. 사용자 메시지

좋은 메시지:
알림 발송에 실패했습니다. 잠시 후 다시 시도해 주세요.

나쁜 메시지:
ProviderError 500: Invalid template mapping exception from kakao gateway

➕ 8-2. 서버 로그

[ERROR] provider=KAKAO_ALIMTALK action=send template=CONSULT_DONE errorCode=TIMEOUT consultId=123
  • 사용자에게는 이해 가능한 메시지를 보여줍니다.
  • 서버 로그에는 원인 분석에 필요한 정보를 남깁니다.
  • 단, 인증키, 토큰, 주민등록번호, 전체 전화번호 같은 민감정보는 로그에 남기면 안 됩니다.

✅ 9. 외부 API 실패 이력 테이블

  • 외부 API 호출 결과는 DB에 이력으로 남겨두는 것이 좋습니다.
  • 특히 알림톡, SMS, 결제, 광고 전환 API는 나중에 운영자가 확인할 수 있어야 합니다.

➕ 9-1. ExternalApiLog 모델 예시

model ExternalApiLog {
  id             Int      @id @default(autoincrement())
  provider       String
  action         String
  status         String
  requestId      String?
  relatedType    String?
  relatedId      Int?
  requestPayload Json?
  responseBody   Json?
  errorCode      String?
  errorMessage   String?
  attempts       Int      @default(0)
  createdAt      DateTime @default(now())
}

➕ 9-2. 주요 필드 설명

필드설명
provider외부 API 업체
action수행한 작업
statusSUCCESS, FAILED, PENDING
relatedTypeCONSULT, ORDER, PAYMENT 등
relatedId연관 데이터 ID
requestPayload요청 내용
responseBody응답 내용
errorCode실패 코드
errorMessage실패 메시지
attempts시도 횟수
  • requestPayload에는 민감정보를 그대로 저장하지 않도록 주의해야 합니다.
  • 전화번호는 마스킹하거나 필요한 최소 정보만 저장하는 것이 좋습니다.

✅ 10. 재시도 전략

  • 외부 API는 일시적인 장애가 있을 수 있으므로 재시도 전략이 필요합니다.
  • 하지만 모든 실패를 무조건 재시도하면 안 됩니다.

➕ 10-1. 재시도할 수 있는 실패

실패재시도 여부
Timeout가능
500 서버 오류가능
502/503/504가능
네트워크 오류가능
429 Rate Limit지연 후 가능

➕ 10-2. 재시도하면 안 되는 실패

실패이유
잘못된 전화번호다시 해도 실패
잘못된 템플릿 코드코드 수정 필요
인증키 오류설정 수정 필요
권한 없음계정/권한 수정 필요
요청 포맷 오류코드 수정 필요
일시적 장애:
재시도 가능

요청 자체가 잘못됨:
재시도보다 원인 수정 필요

✅ 11. Exponential Backoff

  • Exponential Backoff는 실패할수록 재시도 간격을 점점 늘리는 방식입니다.
  • 외부 API 서버에 부담을 주지 않으면서 재시도할 수 있습니다.

➕ 11-1. 예시

1차 실패:
3초 후 재시도

2차 실패:
10초 후 재시도

3차 실패:
30초 후 재시도

최종 실패:
관리자 확인 대상으로 등록

➕ 11-2. BullMQ 예시

await this.notificationQueue.add(
  'send-alimtalk',
  {
    consultId: consult.id,
  },
  {
    attempts: 3,
    backoff: {
      type: 'exponential',
      delay: 3000,
    },
  },
);
  • 재시도 횟수는 반드시 제한해야 합니다.
  • 무한 재시도는 외부 API 비용 증가와 서버 부하를 만들 수 있습니다.

✅ 12. 멱등성

  • 멱등성(Idempotency)은 같은 요청이 여러 번 실행되어도 결과가 중복되지 않게 만드는 성질입니다.
  • 외부 API 연동에서 매우 중요합니다.

➕ 12-1. 왜 필요한가?

결제 승인 API 호출 성공
  ↓
우리 서버가 응답 저장 전에 장애
  ↓
재시도
  ↓
결제가 중복 승인될 수 있음
알림톡 발송 성공
  ↓
성공 로그 저장 전 Worker 장애
  ↓
Job 재시도
  ↓
알림톡이 두 번 발송될 수 있음
  • 결제, 포인트, 쿠폰, 알림 발송처럼 중복 실행이 문제 되는 기능은 멱등성을 반드시 고려해야 합니다.

➕ 12-2. 멱등성 키

  • Idempotency Key는 같은 요청인지 판단하기 위한 고유한 키입니다.
consult-alimtalk:123:CONSULT_COMPLETE
payment:order-20260701-0001
coupon:user-1:event-202607
  • 같은 멱등성 키로 이미 성공한 기록이 있으면 다시 실행하지 않도록 처리할 수 있습니다.

➕ 12-3. 예시

const existing = await this.prisma.externalApiLog.findFirst({
  where: {
    provider: 'KAKAO',
    action: 'SEND_CONSULT_COMPLETE',
    relatedType: 'CONSULT',
    relatedId: consultId,
    status: 'SUCCESS',
  },
});

if (existing) {
  return {
    success: true,
    skipped: true,
  };
}
  • 이미 성공한 작업은 건너뜁니다.
  • 중복 발송과 중복 결제를 방지할 수 있습니다.

✅ 13. Circuit Breaker

  • Circuit Breaker는 외부 API가 계속 실패할 때 일정 시간 동안 호출을 차단하는 패턴입니다.
  • 계속 실패하는 외부 API를 무한히 호출하면 우리 서버도 함께 느려질 수 있습니다.

➕ 13-1. 필요한 상황

외부 API 장애 발생
  ↓
우리 서버가 계속 호출
  ↓
timeout 누적
  ↓
서버 자원 고갈
  ↓
우리 서비스까지 장애

➕ 13-2. 기본 개념

CLOSED:
정상 호출

OPEN:
실패가 많아져 호출 차단

HALF_OPEN:
일부 요청만 테스트 호출
  • 작은 서비스에서는 처음부터 복잡한 Circuit Breaker 라이브러리를 도입하지 않아도 됩니다.
  • 대신 실패율이 높은 외부 API 작업을 Queue로 분리하고, timeout과 재시도 제한을 거는 것부터 시작할 수 있습니다.

✅ 14. Rate Limit 대응

  • 외부 API는 일정 시간 안에 호출 가능한 횟수 제한이 있을 수 있습니다.
  • 429 Too Many Requests가 발생하면 무작정 즉시 재시도하면 안 됩니다.

➕ 14-1. 대응 방법

  1. 외부 API 문서에서 제한 기준 확인
  2. Worker 동시 처리 수 제한
  3. Queue 처리 속도 조절
  4. 429 발생 시 일정 시간 후 재시도
  5. 대량 발송은 배치 단위로 분리
  6. 실패 이력 저장
알림톡 API:
초당 10건 제한

대량 발송:
Worker concurrency를 낮추고 Queue로 순차 처리

✅ 15. 외부 API 설정 관리

  • 외부 API 연동에는 base URL, API Key, Secret, 템플릿 코드, Callback URL 같은 설정값이 필요합니다.
  • 이런 값은 환경변수나 Secret Manager로 관리해야 합니다.

➕ 15-1. .env 예시

KAKAO_API_BASE_URL=
KAKAO_API_KEY=
KAKAO_SENDER_KEY=
KAKAO_CONSULT_TEMPLATE_CODE=

SMS_API_BASE_URL=
SMS_API_KEY=

PAYMENT_API_BASE_URL=
PAYMENT_SECRET_KEY=

➕ 15-2. 주의할 점

  • API Key를 GitHub에 올리면 안 됩니다.
  • 프론트엔드 코드에 Secret을 넣으면 안 됩니다.
  • 개발/운영 키를 분리해야 합니다.
  • 템플릿 코드 변경 시 배포가 필요한지 확인해야 합니다.
  • 외부 업체가 제공한 테스트 환경과 운영 환경을 구분해야 합니다.

✅ 16. Callback / Webhook 처리

  • 외부 API는 처리 결과를 우리 서버로 다시 알려주는 Callback 또는 Webhook을 제공할 수 있습니다.
  • 결제 결과, 본인인증 결과, 문자 발송 결과, 배송 상태 변경에서 자주 사용됩니다.

➕ 16-1. Webhook 흐름

우리 서버가 외부 API 요청
  ↓
외부 API가 비동기 처리
  ↓
외부 API가 우리 Callback URL 호출
  ↓
우리 서버가 결과 검증
  ↓
DB 상태 업데이트

➕ 16-2. Webhook에서 검증할 것

  1. 실제 외부 API 업체에서 온 요청인가?
  2. 서명값 또는 Secret 검증이 필요한가?
  3. 이미 처리한 이벤트는 아닌가?
  4. 연관 주문/상담/결제 데이터가 존재하는가?
  5. 상태 변경이 가능한 흐름인가?
  6. 처리 결과를 이력으로 저장하는가?
주의:
Webhook 요청을 검증하지 않으면
외부에서 임의로 결제 완료/인증 완료 요청을 보낼 수 있음

✅ 17. 외부 API와 트랜잭션

  • 외부 API 호출은 DB 트랜잭션처럼 rollback되지 않습니다.
  • 따라서 DB 트랜잭션 안에서 외부 API를 호출하는 것은 신중해야 합니다.

➕ 17-1. 좋지 않은 예시

await this.prisma.$transaction(async (tx) => {
  const order = await tx.order.create({ data });

  await this.paymentClient.approvePayment(paymentKey);

  await tx.paymentLog.create({
    data: {
      orderId: order.id,
      status: 'APPROVED',
    },
  });
});
  • 결제 승인 API는 이미 성공했는데, 이후 DB 저장이 실패하면 결제만 되고 주문 데이터가 꼬일 수 있습니다.
  • 결제는 취소 API 같은 보상 작업을 고려해야 합니다.

➕ 17-2. 더 안전한 사고방식

1. 주문을 PENDING 상태로 생성
2. 결제 승인 API 호출
3. 결제 성공 시 DB 상태 PAID로 변경
4. DB 저장 실패 시 결제 취소 보상 작업 실행
5. 모든 결과를 이력으로 저장
  • 외부 API와 DB 작업은 “완벽한 하나의 트랜잭션”으로 묶을 수 없다는 점을 이해해야 합니다.
  • 실패 보상, 이력, 재처리 가능성을 설계해야 합니다.

✅ 18. 실무 체크리스트

➕ 18-1. 외부 API 연동 체크리스트

  1. 외부 API 호출에 timeout이 설정되어 있는가?
  2. 외부 API Client가 별도 클래스로 분리되어 있는가?
  3. API Key와 Secret이 환경변수로 관리되는가?
  4. 개발/운영 API 주소와 키가 분리되어 있는가?
  5. 외부 응답을 내부 표준 응답으로 변환하는가?
  6. 실패 원인별로 재시도 여부를 구분하는가?
  7. 외부 API 호출 이력을 DB에 남기는가?
  8. 민감정보가 로그에 남지 않는가?

➕ 18-2. 재시도/Queue 체크리스트

  1. 즉시 결과가 필요 없는 작업은 Queue로 분리했는가?
  2. 재시도 횟수에 제한이 있는가?
  3. Exponential Backoff를 적용했는가?
  4. 400/401/403 같은 영구 실패를 무한 재시도하지 않는가?
  5. 429 Rate Limit 대응이 있는가?
  6. 최종 실패한 작업을 관리자 화면에서 확인할 수 있는가?
  7. Worker가 죽었을 때 알 수 있는가?

➕ 18-3. 멱등성/보상 체크리스트

  1. 같은 작업이 두 번 실행되어도 안전한가?
  2. 이미 성공한 알림/결제/쿠폰 지급을 다시 실행하지 않는가?
  3. Idempotency Key를 설계했는가?
  4. 결제 성공 후 DB 저장 실패 시 보상 작업이 있는가?
  5. S3 업로드 후 DB 저장 실패 시 파일 삭제를 고려했는가?
  6. Webhook 중복 수신을 처리할 수 있는가?

✅ 19. AI를 활용해 외부 API 연동을 설계할 때 질문법

  • 외부 API 연동은 성공 코드만 붙이는 것이 아니라 실패, 재시도, 이력, 보안, 멱등성을 함께 설계해야 합니다.
  • AI에게 요청할 때는 API의 성격과 실패 시 영향도를 같이 알려주는 것이 좋습니다.

➕ 19-1. 좋은 질문 예시

NestJS + Prisma 서비스에서 카카오 알림톡 발송 API를 연동하려고 해.

상황:
1. 상담 신청이 저장되면 신청 완료 알림톡을 보내야 함
2. 상담 신청 저장 자체는 알림톡 실패와 무관하게 성공 처리되어야 함
3. 알림톡 API는 timeout이나 500 오류가 가끔 발생할 수 있음
4. 실패 시 최대 3번 재시도하고 싶음
5. 이미 성공한 알림톡은 중복 발송되면 안 됨
6. 실패한 발송은 관리자 페이지에서 확인하고 재발송할 수 있게 하고 싶음
7. 전화번호와 API Key는 로그에 노출되면 안 됨
8. Redis + BullMQ Queue 사용을 고려 중

요청:
- 동기/비동기 처리 기준
- 외부 API Client 구조
- Queue Job 설계
- 실패 이력 테이블
- 재시도 전략
- 멱등성 처리
- 관리자 재발송 흐름
- 로그 보안 기준
을 실무 기준으로 설명해줘.

➕ 19-2. AI 답변 검증 기준

  1. 상담 신청 저장과 알림톡 발송을 분리하는가?
  2. 외부 API timeout을 설정하라고 하는가?
  3. 실패 원인별 재시도 가능 여부를 구분하는가?
  4. 이미 성공한 알림을 중복 발송하지 않도록 멱등성을 설명하는가?
  5. API Key와 전화번호를 로그에 남기지 말라고 하는가?
  6. 실패 이력과 관리자 재발송 흐름을 제안하는가?
  7. 400/401 같은 영구 실패를 무한 재시도하지 않게 하는가?
  8. 외부 API 호출을 DB 트랜잭션처럼 rollback할 수 없다고 설명하는가?

📌 요약

  • 외부 API 연동은 문자, 알림톡, 결제, 본인인증, 광고 전환 API처럼 우리 서비스가 외부 시스템을 호출하는 구조입니다.
  • 외부 API는 우리 통제 밖에 있기 때문에 timeout, 실패 처리, 재시도, 이력 관리가 필수입니다.
  • 즉시 결과가 필요한 결제/본인인증은 동기 처리가 필요하고, 알림톡/SMS/이메일/광고 전환 API는 Queue를 이용한 비동기 처리가 적합합니다.
  • 외부 API 호출 코드는 별도 Client 클래스로 분리하고, 외부 응답은 내부 표준 응답으로 변환하는 것이 유지보수에 좋습니다.
  • 실패 원인에 따라 재시도 여부를 구분해야 하며, timeout/500/네트워크 오류는 재시도 가능하지만 요청 형식 오류나 인증키 오류는 먼저 원인 수정이 필요합니다.
  • Queue 재시도에는 횟수 제한과 Exponential Backoff를 적용해야 하며, 무한 재시도는 피해야 합니다.
  • 결제, 알림, 쿠폰 지급처럼 중복 실행이 위험한 작업은 멱등성 키와 성공 이력 확인이 필요합니다.
  • Webhook/Callback은 서명 검증, 중복 처리, 상태 전이 검증을 반드시 고려해야 합니다.
  • 외부 API 호출은 DB 트랜잭션처럼 자동 rollback되지 않으므로 실패 보상 작업과 운영 이력 관리가 중요합니다.

0개의 댓글