숨고 서비스의 경계를 넓히다: 파트너 내재화를 위한 OpenAPI 구축기

숨고 서비스의 경계를 넓히다: 파트너 내재화를 위한 OpenAPI 구축기

Tech

안녕하세요, Backend Engineer Harvey 입니다. 이번 시간에는 파트너 내제화를 위한 OpenAPI 구축에 대한 여정을 소개해보고자 합니다.

내부 API를 그대로 개방할 수 없었던 이유

1. 외부 트래픽의 직접적인 영향

파트너 트래픽은 내부 트래픽과 달리 예측이 어렵습니다. 특정 시점에 요청량이 급증할 경우, 그 영향은 내부 서비스로 그대로 전달될 수 있습니다. 특히 실시간 처리가 중요한 매칭 도메인에서는 외부 요인으로 인해 SLA가 저하되는 상황을 허용하기 어렵습니다.

2. 외부 장애의 내부 전파 리스크

외부 시스템은 언제든지 지연되거나 응답하지 않을 수 있습니다. 이러한 상태가 내부 API 호출 지연으로 이어질 경우, 단일 연동 지점을 넘어 도메인 전반으로 문제가 확산할 가능성이 있습니다. 내부 서비스 안정성이 외부 시스템 상태에 종속되는 구조는 장기적인 운영 관점에서 적절하지 않다고 판단했습니다.

3. 내부 API의 도메인 종속성

숨고 내부 API는 다음을 전제로 설계되어 있습니다.

  • 숨고 내부 데이터 모델
  • 내부 인증 및 권한 정책
  • 내부 표준 에러 처리 규칙

외부 파트너는 서로 다른 데이터 구조와 인증 방식을 사용합니다. 이러한 상황에서 내부 API를 그대로 노출할 경우 보안, 일관성, 유지보수 측면에서 구조적 문제가 발생할 가능성이 높습니다.

이러한 이유로 내부 API를 외부에 직접 개방하는 대신, 외부 환경을 전제로 설계한 OpenAPI 전용 계층을 구축했습니다. 해당 계층은 외부 요청을 흡수·조정하고, 내부 서비스는 기존 규칙과 책임을 유지한 채 안정적으로 운영될 수 있도록 보호하는 역할을 수행합니다.

프로젝트 설계 목표

외부 채널링 프로젝트의 방향성은 초기 단계부터 다음과 같이 정의했습니다.

파트너 요구 사항의 표준화

파트너마다 다른 요구 사항을 개별 구현으로 대응할 경우, 구조 복잡도가 빠르게 증가합니다. 이에 따라 확장 가능한 OpenAPI 규격을 정의하고, 파트너 요구 사항을 표준화된 형태로 수용하는 것을 핵심 목표로 설정했습니다.

내부·외부 트래픽의 완전 분리

물리적·논리적 분리를 통해 내부 서비스의 안정성을 보호하면서 외부 확장이 가능하게 아키텍처를 설계했습니다.

외부 전용 인증 체계 구축

내부 인증 체계를 외부에 그대로 노출하지 않고, 파트너별 API Key 및 Scope 기반의 외부 전용 인증 모델을 별도로 설계했습니다.

향후 채널 확장을 고려한 기반 마련

단일 연동에 그치지 않고, 신규 파트너가 추가되더라도 구조 변경 없이 확장할 수 있는 기반을 목표로 했습니다.

진행 과정 1 : 채널 인증

채널 인증 계층

채널 인증은 두 단계로 구성됩니다. 네트워크 레벨의 접근 제어와 애플리케이션 레벨의 API Key 검증입니다.

네트워크 레벨: IP 화이트리스트

외부 요청이 Gateway에 도달하기 전에, 파트너사의 IP를 화이트리스트로 등록해 허용된 출처만 접근할 수 있도록 제한합니다. 등록되지 않은 IP의 요청은 Gateway 앞단에서 차단되므로, 애플리케이션 서버까지 도달하지 않습니다. API Key가 유출되더라도 등록된 IP가 아니면 호출 자체가 불가능하므로, 인증 계층의 첫 번째 방어선 역할을 합니다.

애플리케이션 레벨: API Key 검증

# 실제 인증 흐름 (간소화) def external_channel_auth(req): api_key = req.get_header('채널 API 키') # 1. Redis 캐시 확인 channel_name = redis.get(f'채널:API키:{api_key}') # 2. 캐시 미스 시 DB 조회 후 캐싱 if not channel_name: channel_name = ChannelApiKeysModel.get_channel_name_by_api_key(session, api_key) redis.set(f'채널:API키:{api_key}', channel_name) req.channel_name = channel_name

API Key는 테이블에서 관리되며, 채널명 조회 결과는 Redis에 캐싱해 매 요청마다 DB를 조회하지 않습니다.

인증이 필요한 엔드포인트에서는 '채널 API 키' 헤더를 추가로 검증해 특정 사용자를 식별합니다.

진행과정 2 : 사용자 표준화 계층

외부 파트너의 사용자 정보는 숨고 내부 모델과 구조가 다릅니다. 이메일이 없거나, 비밀번호를 설정할 수 없는 파트너도 있습니다.

이 문제를 해결하기 위해, 파트너가 제공하는 최소 정보만으로 숨고 회원을 생성하는 변환 로직을 두었습니다.

# 파트너가 id만 제공하고 email이 없는 경우 def resolve_email_from_input(id, email, channel_name): if email: return email # email 직접 제공 → 그대로 사용 if looks_like_email(id): return id # id가 이메일 형식 → 그대로 사용 return f'{id}@{무작위}.com' # 그 외 → 자동 생성

예를 들어 파트너 partner-aid: "user123"만 보내면, 내부적으로 user123@partner-a.com이라는 이메일을 생성해 숨고 회원으로 등록합니다. 이렇게 하면 내부 서비스는 외부 채널 사용자인지 인식할 필요 없이 기존 로직을 그대로 사용할 수 있습니다. 전화번호가 함께 오면 정규화 후 저장하고, 이름은 AuthUser.first_name에 매핑합니다.

진행과정 3 : 토큰 변환 계층

외부 파트너에게는 숨고 내부 토큰 대신 외부 전용 JWT를 발급합니다.

Refresh Token은 Redis에 화이트리스트로 관리합니다. 갱신 시 기존 토큰을 소비(삭제)하므로 재사용이 불가능합니다.

파트너의 외부 JWT가 숨고 내부 서비스를 호출해야 할 때는, 토큰 변환 엔드포인트를 통해 숨고 내부 토큰으로 교환합니다:

GET /v1/accounts/channel-token/{access_token}?channel_name=partner-a { "auth_token": "<숨고 내부 토큰>", "has_tel": true }

이 구조 덕분에 외부 JWT와 숨고 내부 인증 체계가 완전히 분리됩니다

외부 연동 아키텍처의 핵심: 분리하되 연결합니다

외부 채널링 구조는 다음 세 가지 레이어로 구성됩니다.

  • 트래픽 분리 계층
  • 인증·인가 계층
  • 데이터 표준화 계층

각 계층은 독립적으로 동작하며, 내부 서비스와 직접 결합하지 않습니다.

1. 내부와 외부를 분리한 트래픽 구조

외부 트래픽은 내부 API Gateway를 통과하지 않고, 외부 전용 진입점을 통해 연동 계층으로 유입됩니다. 이를 통해 다음과 같은 효과를 확보했습니다.

  • 외부 트래픽 폭주로부터 내부 서비스 보호
  • 외부 장애 및 지연이 내부 SLA에 미치는 영향 최소화
  • 파트너별 트래픽 관리 정책 적용 가능
  • 외부 전용 Scaling 정책 운영 가능

2. 외부 전용 인증·인가 레이어

외부 파트너는 OAuth, API Key 등 다양한 인증 방식을 요구합니다. 이를 수용하기 위해 내부 인증 체계와 분리된 외부 전용 인증 레이어를 구성했습니다.

  • 파트너별 고유 API Key 발급
  • 호출 가능한 리소스 및 이벤트 단위 Scope 정의
  • 내부 인증 체계와 완전 분리

이를 통해 보안성과 확장성을 동시에 확보했습니다.

대체 이미지

3. 가입 정책 표준화

외부 파트너의 사용자 정보 구조는 매우 다양합니다. 이메일을 제공하지 않거나 비밀번호 설정이 불가능한 경우도 존재합니다.

이에 따라 다음과 같은 기준을 도입했습니다.

  • 최소 정보 기반 가입 허용
  • 외부 사용자 정보를 내부 표준 사용자 모델로 변환하는 계층 적용
  • 기존 매칭 플로우와의 자연스러운 연결

이 구조를 통해 내부 서비스는 외부 채널 사용자를 인지하지 않고도 기존 로직을 그대로 유지할 수 있습니다.

Step 4 : Webhook 설계

외부 파트너에게 숨고 이벤트를 전달하기 위한 수단으로 Webhook을 사용했습니다. Webhook 설계 시 안정성과 확장성을 주요 기준으로 삼았습니다.

1. Linear Backoff 기반 재시도 정책

파트너 서버의 불안정성을 고려해 재시도 간격을 0.3초씩 증가시키는 선형 백오프 방식을 적용했습니다. 이는 과도한 반복 호출로 인한 부하를 방지하면서도 안정적인 이벤트 전달을 가능하게 합니다.

재시도 정책: Linear Backoff를 선택한 이유

파트너 서버가 일시적으로 응답하지 않을 때, 재시도 전략을 어떻게 가져갈지 고민했습니다. 방지해야 할 문제가 두 가지였습니다.

  1. Retry Storm: 고정 간격(예: 0.3초 고정)으로 재시도하면, 파트너 서버가 장애에서 회복하는 동안에도 동일한 빈도로 요청이 쏟아집니다. 이미 불안정한 서버에 부하를 가중시켜 회복을 오히려 방해한다고 생각했습니다.
  2. 과도한 전달 지연: 지수 백오프는 간격이 1초 → 2초 → 4초 → 8초 로 급격히 늘어납니다. 범용 API 재시도에는 적합하지만, 가입 완료 같은 실시간성이 중요한 이벤트에서는 수십 초 단위의 지연이 발생할 수 있습니다.

선형 백오프(0.3초 → 0.6초 → 0.9초...)는 이 두 문제를 동시에 해결합니다. 재시도 간격을 일정하게 늘려 파트너 서버에 회복 시간을 주면서도, 간격 증가가 예측 가능하고 과도하지 않아 이벤트 전달 지연을 최소화합니다.

2. 설정 중심의 Webhook 확장 구조

Webhook 이벤트가 증가할수록 코드 수정과 배포에 의존하는 구조는 한계에 도달합니다. 이를 방지하기 위해 Webhook을 설정 중심으로 확장 가능하도록 설계했습니다.

설정 요소는 다음과 같이 분리했습니다.

  • host: 이벤트 전달 대상 서버
  • path: 파트너 서버 내 호출 경로
  • hook_type: 이벤트 유형

이를 통해 새로운 이벤트 추가나 변경 시에도 코드 수정 없이 설정만으로 대응할 수 있으며, 운영 중인 시스템의 안정성을 유지한 채 확장이 가능합니다.

마무리

본 OpenAPI 기반 외부 채널링 구축은 숨고의 매칭 경험을 외부 생태계로 확장하기 위한 기반을 마련하는 작업이었습니다.

향후 외부 채널이 증가함에 따라 파트너별 요구 기능과 연동 방식의 차이를 어떻게 공통화하고 추상화할 것인지는 지속적으로 해결해야 할 과제로 남아 있습니다.

내부 안정성을 유지하면서도 다양한 요구를 수용하기 위해, 기능 단위의 표준화와 추상화는 계속해서 고도화해 나갈 예정입니다.

  • #api
  • #backend
  • #python
  • #soomgo
Harvey Lee
Harvey LeeBackend Engineer

모두의 더 나은 삶을 위해
함께 변화를 만들어갈 동료를 기다립니다

채용중인 공고 보기
숨고 서비스의 경계를 넓히다: 파트너 내재화를 위한 OpenAPI 구축기 | 숨고 팀 블로그