쇼핑몰 플랫폼용 배송 API 게이트웨이
Jake Tae-moon Jo · 개발 리드 · https://jake-jo-portfolio.vercel.app/ko/work/commerce-delivery-gateway/
현지 택배사 4곳의 서로 다른 인증·주소 코드·상태 코드를 API 하나로 맞춰, 고객사 서비스가 게이트웨이 하나만 부르면 되게 했습니다.
- 연동한 현지 택배사
- 4곳
- 개발 시작 → 첫 스테이징
- 77일
- 개발 시작 → 운영 기록
- 262일
- 공통 배송 상태
- 9개
문제와 배경
고객사의 배송 관리 서비스는 쇼핑몰 플랫폼에서 쓰는 제품입니다. 이 서비스가 현지 택배사 여러 곳에 송장을 발급하고, 취소하고, 조회해야 했습니다. 택배사마다 인증 방식, 요청 형식, 상태 코드가 달랐고, 인증 방식만 네 가지였습니다.
주소도 맞지 않았습니다. 쇼핑몰 주문의 주소는 행정구역 이름이 든 자유 문자열인데, 택배사 API는 택배사마다 다른 행정구역 코드를 요구했습니다. 배송 관리 서비스와 택배사 사이에서 이 차이를 모두 흡수할 중간 서버가 필요했습니다.
프로젝트 개요
- 기간
- 2020.10 – 2021.12
- 역할
- 단독 개발 (설계·구현·테스트·배포·문서)
- 팀
- 본인 1명 (main 커밋 217개 전부)
- 플랫폼
- REST API 서버 (스테이징·운영)
- 스택
- Node.js · Express · Sequelize · MariaDB · Passport · JWT · axios · winston · PM2 · Mocha · Chai
- 배경
- 쇼핑몰 플랫폼의 배송 관리 서비스(고객사 제품)와 동남아 현지 택배사 여러 곳을 잇는 연동
시스템 구성
배송 관리 서비스는 게이트웨이 API 하나만 부르고, 택배사별 차이는 어댑터·주소 코드 조회·상태 정규화가 흡수한다.
노드를 누르거나 Tab으로 이동해 Enter를 누르면 그 노드의 설명이 강조됩니다. 점선 테두리는 외부 시스템입니다.
- 배송 관리 서비스 → 게이트웨이 API: REST · JWT
- 게이트웨이 API → MariaDB: 암호화 저장
- 게이트웨이 API → 택배사 어댑터
- 택배사 어댑터 → 주소 코드 조회: 이름 → 코드
- 택배사 어댑터 → 택배사 A~D: HTTPS
- 택배사 어댑터 → 상태 정규화
- 택배사 A~D → 웹훅 수신: 상태 웹훅
노드 설명
- 쇼핑몰 플랫폼에서 쓰는 고객사 제품이다. 가입·로그인 뒤 쇼핑몰별 택배사 인증 정보를 등록하고, 송장 발급·조회·취소를 요청한다. API 계약은 시퀀스 다이어그램 6장으로 고객 측과 맞췄다.
- MariaDB와 Sequelize다. 모델 5개(사용자, 택배사, 인증 정보, 배송, 품목), 마이그레이션 20개, 시드 4개다. 개인정보와 택배사 인증 정보 35개 필드는 쓰기 직전에 AES로 암호화하고 읽을 때 복호화한다.
- Express REST API다. /api/health와 /api/v1 아래 13개를 합쳐 엔드포인트 14개다. 인증은 JWT(passport-jwt)이고, 로그인에는 무차별 대입 차단(DB 저장소)과 비밀번호 강도 규칙을 걸었다. 요청 ID를 응답 헤더와 로그에 남기고, PM2 클러스터로 돌린다.
- 택배사 A가 보내는 상태 웹훅을 받아 공통 상태로 바꾸고, 저장된 배송 상태에 바로 반영한다. 그와 별개로, 배송을 조회할 때 마지막 갱신이 1시간(설정값)을 넘은 건만 택배사에 다시 묻는다.
- 서버가 시작할 때 택배사별 주소 코드 표 5개를 메모리에 올린다. 요청마다 행정구역 이름을 그 택배사의 코드로 바꾼다. 정확히 일치하는 이름을 먼저 찾고, 없으면 유사도로 맞추되 기준(중간 행정구역 0.89, 하위 행정구역 0.95)에 못 미치면 400을 돌려준다.
- 택배사마다 요청 변환기와 호출 서비스를 따로 둔다. 4곳 모두 생성·취소·조회 세 동작을 같은 모양으로 맞췄다. 인증 정보는 쇼핑몰×택배사마다 하나이고, 인증 방식은 4종(고정 토큰, ID·비밀번호, ID·비밀번호+고객 코드, 고객 ID)이다.
- 현지 택배사 4곳의 API다. 택배사 A는 상태 웹훅도 보내고, B는 토큰 로그인, C는 서명 키 방식이다. 다섯 번째 택배사는 인증 정보 등록과 주소 코드 표까지만 넣었다.
- 택배사마다 다른 상태 코드를 공통 상태 9개(준비, 집하, 취소, 운송 중, 배송 실패, 반송 중, 반송 완료, 배송 완료, 예외)로 바꾼다. 맞는 값이 없으면 unknown으로 둔다.
만든 것
- API 계약을 먼저 맞췄습니다. 가입·인증, 인증 정보 등록·조회, 택배사 등록, 송장 발급, 상태 변경 hook, 발급 취소를 시퀀스 다이어그램 6장으로 그려 고객 측과 합의했습니다.
- REST API 엔드포인트 14개를 만들었습니다. 인증은 JWT로 하고, 로그인에는 무차별 대입 차단과 비밀번호 강도 규칙을 걸었습니다. 개인정보와 택배사 인증 정보 35개 필드는 저장 직전에 AES로 암호화합니다.
나머지 1개
- 택배사 어댑터 4개를 같은 모양으로 맞췄습니다. 택배사마다 요청 변환기와 호출 서비스를 두고, 생성·취소·조회 세 동작을 같은 형태로 냈습니다. 인증 정보는 쇼핑몰×택배사마다 하나이고, 인증 방식은 4가지입니다.
나머지 2개
- 택배사마다 다른 상태 코드를 공통 상태 9개로 모았습니다. 저장된 상태는 두 길로 갱신됩니다. 조회할 때 마지막 갱신이 1시간을 넘은 건만 택배사에 다시 묻고, 택배사 A의 상태 웹훅은 받는 즉시 반영합니다.
- 운영에 필요한 것을 붙였습니다. 요청 ID 헤더와 로그, 일별 회전 로그, 운영에서 끄는 Swagger UI, PM2 클러스터, 스테이징·운영 배포 스크립트입니다. 테스트는 개발 사흘 뒤부터 함께 썼고, 활성 테스트 케이스는 79개입니다.
핵심 결정
기준에 못 미치는 주소는 보내지 않고 거절한다결정 코드 표를 받는 추출 도구와 주소 코드를 찾는 조회 라이브러리를 따로 직접 만들어 붙였습니다.결과 기준값은 2020년 11월 이후 바뀌지 않았고, 2021년 6월 운영 패치에서 택배사별 주소표를 모두 갱신했습니다.
- 문제
- 쇼핑몰 주문의 주소는 행정구역 이름이 든 자유 문자열인데, 택배사 API는 택배사마다 다른 행정구역 코드를 요구했습니다. 그 코드 표를 얻는 방법도 택배사마다 달랐습니다(API 3곳, 엑셀 파일 2곳).
- 결정
- 코드 표를 받는 추출 도구와 주소 코드를 찾는 조회 라이브러리를 따로 직접 만들어 붙였습니다. 추출 도구는 행정 단위어 같은 표기 차이를 지운 뒤 표를 CSV로 씁니다. 정확히 일치하는 이름을 먼저 찾고, 없으면 유사도로 맞춥니다. 기준은 중간 행정구역 0.89, 하위 행정구역 0.95이고, 상위 행정구역은 정확히 일치해야 합니다. 기준에 못 미치면 택배사에 보내지 않고, 필드별 오류와 함께 400을 돌려줍니다.
- 결과
- 기준값은 2020년 11월 이후 바뀌지 않았고, 2021년 6월 운영 패치에서 택배사별 주소표를 모두 갱신했습니다. 중간·하위 행정구역 이름이 조금 틀려도 찾는 경우와 틀린 주소는 찾지 않는 경우를 라이브러리 단위 테스트 케이스로 적어 두었습니다.
- 대가·남은 위험
- 가장 높은 점수 하나만 보고, 1위와 2위의 점수 차는 검사하지 않습니다. 고른 하위 행정구역을 응답에 돌려주지 않아, 호출한 쪽은 주소가 어느 하위 행정구역으로 맞춰졌는지 볼 수 없습니다. 게이트웨이에는 사람이 확인하는 단계가 없고, 기준값을 정한 이유도 기록에 없습니다.
송장 한 건이 발급되는 길
택배사 호출 앞에서 주소와 요청을 그 택배사 형식으로 바꾸고 뒤에서 응답을 공통 형태로 되돌려, 어느 택배사든 같은 요청과 같은 응답이 된다.
- 발급 요청외부
배송 관리 서비스가 JWT와 주문·주소·품목을 보냄 (POST /api/v1/shippings)
- 입력 검증시스템
필수값, 전화번호 형식
- 인증 정보 찾기시스템
이 쇼핑몰이 이 택배사에 등록한 인증 정보. 없으면 404
- 택배사 형식으로 변환시스템
빈 구매자명은 수령인명으로, 주소 이름은 그 택배사의 행정구역 코드로(못 찾으면 400), 전화번호는 정규화
- 택배사 API 호출외부
택배사별 어댑터
- 응답 통일시스템
송장번호·운임을 공통 형태로
- 저장시스템
배송·품목 저장, 개인정보 필드는 암호화
- 응답시스템
저장된 배송 정보를 JSON으로 돌려줌
결과
택배사 A 연동을 끝내고 첫 스테이징에 올렸고, 택배사 4곳을 넉 달에 걸쳐 차례로 붙였습니다.
- 택배사 A의 생성·조회·취소를 끝낸 뒤 첫 스테이징에 올렸습니다.
- 택배사 4곳을 한 곳씩 붙여, 배송 관리 서비스는 어느 택배사든 같은 요청과 같은 응답으로 다룰 수 있게 했습니다.
나머지 결과 1건
- 개발 시작 262일째에 운영 중이라는 첫 기록(운영 패치 노트)이 나왔습니다.
나머지 결과 1건
- 그 뒤에도 한 배송에 여러 품목을 담도록 품목 모델과 택배사별 요청을 고쳤습니다.
코드와 커밋 기록으로 확인한 범위만 적었습니다. 운영 규모와 운영 기간은 적지 않았습니다.
시작에서 운영 기록까지
개발 77일 만에 첫 스테이징에 올렸고, 택배사를 한 곳씩 붙여 262일째에 운영 패치 노트가 나왔다.
- 첫 스테이징까지
- 77일
- 운영 패치 노트까지
- 262일
- 개발 시작
- 택배사 A 연동
생성·조회·취소, 12월 11일까지
- 첫 스테이징 배포
시작 후 77일
나머지 6개
- 택배사 B 연동
- 택배사 C 연동
3월 24일 검수
- 택배사 D 연동
4월 15일까지
- 운영 준비
무차별 대입 차단, 전화번호 검증, 운영 배포 스크립트, InnoDB 전환(5월 20일까지)
- 운영 패치 노트
운영 중이라는 첫 기록, 시작 후 262일
- 한 배송에 여러 품목
main 마지막 커밋 12월 29일