기술 기록

블로그가 수집 서비스 DB를 직접 읽던 구조를 HTTP 데이터 계약으로 바꾼 기록

개개발 노하우 노트2026년 11월 11일 · 4분 읽기

개인 기술 블로그가 데이터 수집 서비스의 DB를 직접 읽던 구조를 걷어내고, 수집 서비스가 공개하는 HTTP 데이터 계약으로만 읽도록 바꿨다. 두 서비스가 같은 스키마에 묶여 있으면 한쪽 마이그레이션이 다른 쪽 배포를 깨뜨리기 때문이다. 서비스 사이의 DB 공유를 정리하려는 개발자에게 도움이 되도록 결정 과정과 구현 요점을 정리한다.

배경

블로그의 트렌드 화면은 수집 서비스가 모은 유튜브 영상과 기술 뉴스를 보여 준다. 처음에는 빠르게 만들려고 블로그가 수집 서비스 소유의 테이블을 ORM으로 직접 조회했다. 채널 목록, 영상 목록, 뉴스, 통계, 검색이 모두 이 방식이었다.

시간이 지나며 문제가 드러났다. 수집 서비스가 컬럼을 정리하려 해도 블로그 코드가 그 컬럼을 읽고 있어 함부로 지울 수 없었다. 채널과 영상의 관계도 채널 이름으로 이어 붙이고 있었는데, 이름이 같은 다른 채널이 있으면 영상이 엉뚱한 채널로 합쳐졌다. 블로그 배포 설정에도 남의 DB 접속 정보가 들어가 있었다.

선택지와 결정

선택지는 세 가지였다. 읽기 전용 DB 계정과 뷰를 두는 방법, 공용 패키지로 스키마를 공유하는 방법, 수집 서비스가 HTTP API를 제공하는 방법이다. 뷰와 공용 패키지는 결국 스키마 결합이 남는다. 그래서 수집 서비스가 계약을 소유하고 블로그는 그 계약만 아는 HTTP 방식을 택했다.

계약은 먼저 문서로 고정했다. 날짜 범위는 시작 포함·끝 미포함으로 정했고, 검색은 제목·요약·출처에서 대소문자를 무시하는 포함 검색으로 정했다. 관계는 이름 대신 채널 ID와 영상 ID로만 잇는다. 이름으로 JOIN하는 것은 금지하고, ID를 확인할 수 없는 과거 데이터는 추측해서 채우지 않고 비워 두기로 했다. 수집 서비스는 이후 공식 RSS에 들어 있는 채널 정보로 확인 가능한 행만 안전하게 백필했다.

구현 요점

블로그 쪽에는 서버에서만 동작하는 작은 HTTP 클라이언트를 하나 두었다. 접속 주소는 서버 전용 환경 변수로만 받고, 브라우저에서 호출되면 바로 오류를 낸다. 핵심은 응답을 믿지 않는 것이다. fetch는 4xx·5xx에서 예외를 던지지 않으므로 상태 코드를 직접 확인하고, 본문이 JSON인지, `success`가 참인지, 데이터 모양이 계약과 같은지를 타입 가드로 모두 검사한다.

```js

async function getCollector(path, validate) {

let res

try {

res = await fetch(collectorUrl(path), { cache: 'no-store' })

} catch {

throw new CollectorApiError('COLLECTOR_UNAVAILABLE', '수집 API에 연결할 수 없습니다.', 503)

}

const payload = await res.json().catch(() => null)

if (!res.ok) throw toApiError(payload, res.status)

if (!isRecord(payload) || payload.success !== true || !validate(payload.data)) {

throw new CollectorApiError('COLLECTOR_INVALID_RESPONSE', '응답 계약이 올바르지 않습니다.', 502)

}

return payload.data

}

```

화면에서 가장 신경 쓴 부분은 "데이터가 0건"과 "가져오지 못함"을 구분하는 것이다. 예전 코드는 조회가 실패하면 빈 배열을 돌려줘서, 사용자는 오늘 수집된 영상이 없는 것인지 연동이 끊긴 것인지 알 수 없었다. 이제는 정상 0건, 연동 오류, 마지막 정상 응답을 보여 주는 지연 상태를 서로 다른 화면으로 그린다.

운영자 해설

이번 전환에서 가장 큰 이득은 배포 순서가 분리된 것이다. 수집 서비스는 계약만 지키면 내부 컬럼을 마음대로 정리할 수 있고, 실제로 전환이 끝난 뒤 옛 컬럼을 삭제했다. 비용도 있다. 네트워크 한 단계가 늘었고, 계약 변경은 양쪽 합의가 필요하다. 개인 프로젝트 규모에서도 서비스가 둘 이상이고 배포 주기가 다르다면 이 비용을 낼 가치가 있다고 판단했다.

ID 기반 관계로 바꾸자 과거 데이터 상당수의 채널 연결이 비어 있다는 사실이 드러났다. 이름 매칭으로 채우면 숫자는 보기 좋아지지만 동명 채널이 다시 섞인다. 비어 있는 것을 비어 있다고 보여 주는 편이 맞다.

운영하면서 알게 된 것

  • 계약 문서를 먼저 확정하면 양쪽 구현이 병렬로 진행된다.
  • 같은 입력으로 수집 서비스 원본 응답과 블로그 화면 결과를 대조하는 검사가 전환 검증의 중심이다.
  • 실패를 빈 배열로 숨기지 않는다. 정상 0건과 오류는 다른 상태다.
  • 이름이 아니라 ID로 관계를 잇고, 확인할 수 없는 값은 비워 둔다.
  • DB 접속 정보가 소비자 쪽 설정에서 사라졌는지 마지막에 확인한다.

참고 자료

블로그가 수집 서비스 DB를 직접 읽던 구조를 HTTP 데이터 계약으로 바꾼 기록 | 개발 노하우 노트