프로젝트
지마켓삼성카드 첫 적용

외부 파트너 제휴 연동 플랫폼

지마켓·옥션 회원과 외부 파트너를 잇는 제휴 연동의 첫 서비스입니다. 두 앱에서 발생하는 동의를 맞추는 일부터 정합 배치, 탈회, 리워드, 그리고 운영팀이 쓰는 어드민까지 한 묶음으로 만들었습니다.

기간
2026.01 — 2026.06 개발, 이후 운영
2인 팀
역할
주요 개발자
기술
  • Java 21
  • Spring Boot 3.2
  • WebFlux
  • Spring Batch
  • Oracle
  • MongoDB
  • Next.js
  • SFTP

만든 것

제휴 연동 서비스 동의 · 정합 · 탈회 · 리워드를 한 서비스로 실시간 동의 본인 인증 커밋 이후(AFTER_COMMIT) → 동의 · 이력은 새 트랜잭션(REQUIRES_NEW) 이미 동의된 항목은 성공으로 분류해 같은 요청이 와도 결과가 같음 일배치 정합 묶음 수신 · 마스터 행 비관적 락 마지막 묶음에서 누락 번호 · 건수 비교 월배치 파일 Spring Batch chunk · SFTP 레코드 오류는 skip, 시스템 오류는 실패 탈회 연동 회원 처리와 외부 반영 실패 분리 리워드 응모 외부 호출 실패 → 실패 기록 보관 Oracle 동의 · 이력 · 배치 마스터 MongoDB 리워드 실패 기록 · 유니크 인덱스 동의가 생기는 곳 지마켓 앱 안내 화면에서 동의 제휴사 앱 제휴사 화면에서 동의 양쪽에서 발생하는 동의를 한 상태로 맞추는 것이 과제 제휴사 시스템 동의 API 실시간 수신 · 회신 일배치 전송 묶음 단위 · 건수 SFTP 파일 월 단위 리워드 응모 API 외부 지급 - - 게이트웨이 신뢰 경계 · 우회 접근 차단은 인프라 요구사항 운영 어드민 (Next.js) 제휴사 관리 동의 조회 배치 모니터링 리워드 실패 목록 개인정보 접근 감사 연동 이력과, 그 이력을 누가 조회했는지의 기록을 분리 · 보관 기간 차등 상태 확인 · 재처리 근거
기능 사이의 책임을 그린 개념도입니다. 실제 네트워크와 배포 구조는 다릅니다.
  • 실시간 동의본인 인증 이후의 동의 저장, 제휴사에서 오는 동의 수신
  • 일배치 정합묶음으로 나뉘어 오는 일배치의 누락 · 건수 확인
  • 월배치 파일SFTP 파일을 chunk 단위로 적재, 레코드 오류 분리
  • 탈회 연동회원 탈퇴 처리와 외부 반영의 실패를 분리
  • 리워드 응모외부 응모 호출, 실패 기록 보관
  • 운영 어드민제휴사 관리 · 동의 조회 · 배치 모니터링 · 접근 감사

어떤 상황이었나

카드사 같은 외부 제휴사와 회원 데이터를 주고받는 표준 채널이 없었습니다. 제휴가 하나 생길 때마다 연동 방식을 처음부터 다시 설계해야 했고, 이번에는 첫 제휴사를 붙이면서 다음 제휴사도 같은 구조로 붙일 수 있게 만드는 것이 목표였습니다.

까다로운 점은 동의가 한 곳에서 나오지 않는다는 것이었습니다. 지마켓 앱의 안내 화면에서도, 제휴사 앱에서도 동의가 발생합니다. 한 방향으로 API를 호출하는 것만으로는 두 시스템의 동의 상태가 맞지 않고, 외부 연동이 실패했다고 회원 탈퇴 같은 핵심 처리가 막혀서도 안 됐습니다. 2인 팀에서 주요 개발자로 설계와 구현, 이후 운영까지 맡았습니다.

동의 하나에도 순서가 있다

본인 인증이 끝난 뒤 동의를 저장하는 흐름은 두 트랜잭션으로 나눴습니다. 인증 성공 이벤트를 커밋 이후에 받아서, 동의와 동의 이력은 새 트랜잭션으로 저장합니다. 동의 저장이 실패해도 이미 끝난 인증을 되돌리지 않고, 두 실패를 따로 다루기 위해서였습니다. 같은 항목에 이미 동의가 있으면 성공으로 분류해서, 같은 요청이 두 번 와도 결과가 달라지지 않게 했습니다.

다만 커밋 직후 프로세스가 죽는 경우의 복구와, 항목별로 일부만 실패했을 때 그것을 로그와 알림으로 드러내는 일은 아직 보완할 부분으로 남아 있습니다.

묶음으로 오는 배치

일배치는 하루치 데이터가 여러 묶음으로 나뉘어 들어오고, 묶음들이 거의 동시에 도착하기도 합니다. 처음 마스터 행을 만드는 경합과 이미 있는 행에 누적하는 경합은 실패하는 방식이 달라서 따로 처리했습니다. 마스터 생성은 별도 트랜잭션에서 유니크 경합을 받아내고, 누적 갱신은 마스터 행에 비관적 락을 걸어 순서대로 반영합니다. 같은 묶음 번호가 다시 오면 누적하지 않고, 마지막 묶음이 들어오면 빠진 번호와 건수를 비교합니다.

이 정합 확인은 묶음 누락과 건수 일치까지입니다. 양쪽 회사의 회원 상태가 의미적으로 같은지, 오래된 동의와 철회 이벤트 중 무엇이 우선인지는 다른 층의 문제라 이 코드로 보장한다고 말하지 않습니다.

실패를 어디까지 책임질 것인가

리워드 응모는 외부 시스템을 호출합니다. 호출이 실패하면 MongoDB에 기록해 두고, 제휴사 · 회원 · 이벤트 조합에 유니크 인덱스를 걸어 같은 실패가 두 번 쌓이지 않게 했습니다. 사용자 흐름을 막지 않으면서 나중에 확인하고 재처리할 근거를 남기는 구조입니다.

이건 실패 기록을 보관하는 구조이지, 업무 데이터와 같은 트랜잭션에 발행 작업을 쓰는 Transactional Outbox는 아닙니다. 실패 기록의 중복을 막는 것과 외부 지급이 두 번 나가지 않는 것도 다른 이야기라서, 면접이나 문서에서 이 둘을 섞어 말하지 않으려고 합니다.

월배치는 Spring Batch chunk 처리로 파일을 적재하는데, 레코드 검증이나 복호화 오류는 제한된 수까지 건너뛰고 재암호화 같은 시스템 오류는 작업 실패로 남겼습니다. 한 행의 데이터 문제로 전체 파일을 멈추지는 않되, 시스템 문제는 조용히 넘기지 않기 위해서입니다.

만들고 끝이 아니라 운영까지

연동 기능만 만들어 놓으면 운영팀은 상태를 확인할 방법이 없습니다. 제휴사 관리, 동의 조회, 배치 모니터링, 리워드 실패 목록을 볼 수 있는 어드민을 Next.js로 함께 만들었습니다.

개인정보가 오가는 서비스라서 기록도 두 겹으로 남겼습니다. 연동 이력 자체와, 그 이력을 누가 조회했는지 남기는 감사 기록을 분리하고 보관 기간을 다르게 뒀습니다. 애플리케이션에 인증 가드가 없다는 점은 의존성과 설정을 점검해 확인하고, 게이트웨이를 신뢰 경계로 문서화하면서 우회 접근 차단은 인프라 요구사항으로 명시했습니다. 정적 점검이라 실제로 우회가 불가능하다는 것까지 증명한 건 아닙니다.

돌아보면

이 프로젝트에서 가장 많이 배운 건 "성공했다"보다 "어디까지 보장하는가"를 정확히 말하는 태도였습니다. 트랜잭션 경계, 중복 판정, 실패 기록이 각각 무엇을 지키고 무엇을 지키지 않는지 나눠서 보게 됐습니다.

회사 업무는 공개 가능한 수준으로만 적었습니다. 내부 코드, 시스템 이름, 고객 정보와 운영 수치는 넣지 않았고, 팀이 함께 한 일과 제가 한 일을 구분했습니다.