금융 데이터 API의 스키마 진화: 소비자에게 문제를 일으키지 않는 버전 관리 방법
요약
금융 데이터 API의 스키마 진화는 다운스트림 서비스에 심각한 오류를 유발할 수 있으므로, 변화 자체보다 예측 가능하게 만드는 것이 중요합니다. 필드 추가 시에는 새 필드를 먼저 도입하고 기존 필드와 동일한 값을 유지하는 다단계 마이그레이션 전략을 사용해야 합니다.
핵심 포인트
- 스키마 변경은 일반적인 호환성 위험 외에 의미론적 깨짐 변화를 주의해야 함.
- 필수/선택 필드, Null 가능성, 단위 관례 등 모든 측면의 명확한 문서화가 필수적임.
- 새로운 필드를 도입할 때는 기존 필드를 유지하며 다단계 마이그레이션 전략을 사용해야 함.
금융 데이터 API는 오늘 완벽하게 작동하더라도 내일 다운스트림 서비스에 오류를 발생시킬 수 있습니다.
다음과 같이 신용 계좌를 JSON으로 반환하는 API를 상상해 보세요:
{
"account_id": "acc_1024",
"balance": "12500.00",
...
}
소비자 서비스는 balance를 읽어 십진수로 변환한 다음 월별 요약을 생성합니다.
나중에 API 팀은 응답을 개선하기로 결정합니다. 어쩌면 balance가 current_balance가 될 수도 있고, 새로운 상태가 도입될 수도 있으며, 심지어 응답에 Nullable 필드가 포함되기 시작할 수도 있습니다.
이러한 변화는 API 팀의 관점에서는 무해해 보일 수 있습니다. 하지만 소비자가 원래 필드를 예상하고 있다면, 배포가 갑자기 실패하거나 불완전한 결과를 생성할 수 있습니다.
이것이 바로 스키마 진화 문제입니다.
금융 시스템에서 스키마 변경은 특별한 주의를 요합니다. 데이터가 보고 서비스, 분석 파이프라인, 조정(reconciliation) 작업, 고객 대시보드, 감사 시스템을 거칠 수 있기 때문입니다.
목표는 변화 자체를 막는 것이 아닙니다. 변화를 예측 가능하게 만드는 것입니다.
1. 추가적 변경과 깨지는 변경 분리하기
모든 스키마 변경이 동일한 위험도를 갖는 것은 아닙니다.
다음 예시들을 고려해 보세요:
| 변경 | 일반적인 호환성 위험 |
|---|---|
| 선택적 필드 추가 | 낮음, 소비자가 알 수 없는 필드를 허용할 경우 |
| ... | |
| 마지막 두 경우는 과소평가하기 쉽습니다. |
통화 가치를 루피(rupees)에서 파이시(paise)로 변경하면서 필드 이름은 그대로 유지하는 것은 JSON 타입이 문자열로 남아 있더라도 의미론적 깨짐 변화(semantic breaking change)입니다.
마찬가지로, status가 `
- 필수 및 선택 필드
- Null 가능성 규칙
- 숫자 정밀도 및 표현 방식
- 통화 및 단위 관례
- 허용되는 상태 값
- 타임스탬프 형식 및 시간대
- 누락, 알 수 없음, 그리고 0 값의 의미
- 사용 중단(Deprecation) 및 제거 정책
예를 들어, 돈을 십진수 문자열로 표현하면 이진 부동 소수점 산술을 사용하는 클라이언트에서 실수로 발생하는 부동 소수점 변환을 방지할 수 있습니다.
{
"balance": "12500.00",
"currency": "INR"
...
}
이러한 표현 방식이라도 문서화된 정밀도 및 반올림 정책이 필요합니다. 문자열만으로는 정확한 금융 산술을 보장할 수 없습니다.
Python에서는 십진수 산술을 수행할 때 Decimal을 사용하세요:
from decimal import Decimal
balance = Decimal("12500.00")
...
정확한 십진수 입력이 가능한 경우, 이미 부정확한 float에서 Decimal을 구성하는 것을 피하세요.
3. 소비자에게 사용하도록 요구하기 전에 필드 추가하기
원래 API가 balance를 노출하고 팀이 current_balance를 도입하려고 한다고 가정해 보겠습니다.
더 안전한 마이그레이션은 여러 단계를 거칩니다.
1단계: 새 필드 추가.
{
"balance": "12500.00",
"current_balance": "12500.00",
...
}
마이그레이션 기간 동안 두 필드는 동일하게 문서화된 값을 나타내야 합니다.
2단계: 소비자 업데이트.
current_balance를 읽을 수 있는 소비자 코드를 배포하되, 해당 폴백(fallback)이 마이그레이션 계약의 일부인 경우 balance만 포함된 응답과도 호환되어야 합니다.
3단계: 사용량 측정.
로그, 메트릭 또는 소비자 수준 텔레메트리를 사용하여 활성 클라이언트 중 여전히 이전 필드에 의존하는 것이 있는지 파악합니다.
4단계: 이전 필드 사용 중단(Deprecate).
사용 중단 공지(deprecation notice)를 게시하고 영향을 받는 소비자들에게 제거 일정을 전달합니다.
5단계: 호환성 기간 이후에만 이전 필드 제거.
정확한 일정은 API의 소비자, 릴리스 프로세스, 운영 요구 사항에 따라 달라집니다.
이 접근 방식은 모든 서비스가 동시에 마이그레이션되는 것을 방지합니다.
4. 누락된 값(missing), null, 그리고 0을 서로 다른 상태로 취급하기
금융 데이터는 종종 부재하는 값을 신중하게 처리해야 합니다.
다음 세 가지 응답을 고려해 보세요:
{
"minimum_due": "0.00"
}
{
"minimum_due": null
}
{}
이들은 서로 다른 의미를 가질 수 있습니다:
"0.00": 알려진 최소 납부액이 0이라는 의미입니다.null: 필드는 존재하지만, 계약(contract)상 값은 알 수 없거나 사용할 수 없다는 의미입니다.- 누락된 필드 (Missing field): 데이터를 제공하는 측(producer)에서 해당 필드를 제공하지 않은 경우일 수 있으며, 이는 선택 사항이거나 스키마 버전이 다르기 때문일 수 있습니다.
이러한 상태들이 상호 교환 가능하다고 절대 가정해서는 안 됩니다.
누락된 값을 0으로 변환하는 소비자(consumer)는 불완전한 데이터를 실수로 유효해 보이는 금융 결과물로 변형할 수 있습니다.
의미론적 측면을 명시적으로 정의하고, 각 상태를 테스트해야 합니다.
5. 호환되지 않는 변경 사항을 포착하기 위해 계약 테스트(contract tests) 사용하기
스키마 문서화는 도움이 되지만, 자동화된 검사가 호환성을 더욱 신뢰할 수 있게 만듭니다.
간단한 Pydantic 모델은 들어오는 응답의 구조를 검증할 수 있습니다:
from decimal import Decimal
from typing import Literal
from pydantic import BaseModel, ConfigDict
...
이 모델은 필수 필드와 인식된 상태 값에 대해서는 의도적으로 엄격하지만, 알 수 없는 추가 필드는 무시합니다.
이는 가능한 소비자 정책 중 하나일 뿐이며, 보편적인 선택은 아닙니다. 일부 시스템은 알려지지 않은 열거형(enum) 값을 거부해야 하며, 다른 시스템은 순방향 호환성(forward compatibility)을 위해 이를 보존해야 합니다. 위험도와 애플리케이션의 예상 동작에 따라 결정하세요.
유용한 테스트 스위트에는 다음 내용이 포함되어야 합니다:
- 현재 제공자로부터 받은 유효한 응답.
- 선택적 필드가 생략된 응답.
- 추가적인 알 수 없는 필드를 포함하는 응답.
- 필수 필드가 누락된 응답.
- null이 허용되지 않는 곳에
null을 포함하는 응답. - 지원되지 않는 상태를 가진 응답.
- 유효하지 않은 통화 정밀도 또는 형식을 가진 응답.
더 강력한 보장을 위해, 새로운 프로듀서 스키마를 이전 버전의 대표 페이로드와 비교하여 검증하고, 여러 서비스가 API에 의존하는 소비자 주도 계약 테스트(consumer-driven contract tests)를 실행해야 합니다.
6. 구조적 호환성뿐만 아니라 의미론적 호환성을 테스트하라
두 스키마는 동일해 보일 수 있지만 서로 다른 것을 나타낼 수 있습니다.
예를 들어, balance라는 필드는 다음을 의미할 수 있습니다:
- 현재 미결제 잔액(The current outstanding balance)
- 명세서 잔액(The statement balance)
- 다음 결제일에 납부해야 할 금액(The amount due on the next payment date)
- 이전 보고일자 기준의 잔액(The balance as of a previous reporting date)
네 가지 값 모두 십진수 문자열(decimal strings)일 수 있습니다. 구조적 스키마 검증기(structural schema validator)는 이 네 가지 값을 모두 허용할 수 있습니다.
하지만 이것이 이들을 상호 교환 가능하게 만든다는 의미는 아닙니다.
재정적 결정을 좌우하는 필드에 대해서는 비즈니스 의미, 유효 날짜, 출처(source), 계산 규칙을 문서화해야 합니다.
정의가 변경될 때는, 필드 이름과 데이터 유형이 동일하더라도 계약 변경으로 취급해야 합니다.
7. 버전을 의도적으로 관리하라 (Version intentionally)
API 버전 관리에 여러 가지 일반적인 접근 방식이 있습니다.
URL 버전 관리(URL versioning)
/api/v1/accounts
/api/v2/accounts
이는 주요 버전을 눈에 띄게 하고 라우팅하기 쉽게 만들지만, 여러 버전을 유지하는 것은 운영 복잡성을 증가시킬 수 있습니다.
미디어 타입 또는 헤더 버전 관리(Media-type or header versioning)
클라이언트가 요청 헤더를 통해 계약 버전을 지정합니다. 이는 URL을 안정적으로 유지하지만, 버전 정보가 명확하게 기록되지 않으면 디버깅이 덜 직관적일 수 있습니다.
하나의 버전 내에서 추가적인 진화(Additive evolution within one version)
호환 가능한 변경 사항은 새로운 주요 버전을 만들지 않고 도입됩니다. 하위 호환성을 깨는 변경(Breaking changes)에는 별도의 마이그레이션 전략이 필요합니다.
많은 시스템의 경우, 실용적인 규칙은 버전 내에서 하위 호환성 추가를 허용하고 비호환적 변경 사항에 대해서는 문서화된 프로세스를 사용하는 것입니다.
중요한 부분은 일관성입니다. 버전 관리는 단순히 배포 번호를 나타내는 것이 아니라 실제 계약 경계를 설명해야 합니다.
8. 이전 필드를 제거하기 전에 사용 중단(deprecation) 상태를 모니터링하라
사용 중단 공지(deprecaton announcement)가 그 필드가 사용되지 않음을 증명하지는 않습니다.
다음과 같은 관련 신호들을 추적해야 합니다:
- 클라이언트가 요청한 버전
- 관찰 가능한 경우 사용된 사용 중단 필드(deprecated fields)
- 계약 테스트 실패(Contract-test failures)
- 배포 후 오류율(Error rates after deployments)
- 소비자 마이그레이션 진행 상황(Consumer migration progress)
- 예상치 못한 null 값 또는 파싱률(Unexpected null or parsing rates)
민감한 금융 페이로드(financial payloads)를 관찰하기 위해 로깅하는 것은 피하세요. 대신, 낮은 위험도의 메타데이터와 집계된 지표(aggregate metrics)를 선호하고, 적절한 접근 제어 및 보존 제한을 적용하세요.
만약 소비자 식별이 불가능하거나 사용량을 신뢰성 있게 측정할 수 없다면, 더 보수적인 제거 계획을 사용하세요.
9. 스키마 마이그레이션 체크리스트 유지하기
스키마 변경 사항을 출시하기 전에 다음 질문들을 던지세요:
- 변경 사항은 구조적(structural)인가요, 의미론적(semantic)인가요, 아니면 둘 다인가요?
- 기존 소비자들이 계속 작동할까요?
- 누락된 값과 null 값은 올바르게 처리되나요?
- 이전 페이로드들이 테스트되었나요?
- 금액(money) 및 타임스탬프 관례가 문서화되어 있나요?
- 마이그레이션 또는 롤백 계획이 마련되어 있나요?
- 소비자들이 사용 중단 날짜를 인지하고 있나요?
- 민감한 데이터를 노출하지 않으면서 사용량을 모니터링할 수 있나요?
- 스키마 및 계약 테스트가 CI(Continuous Integration)의 일부인가요?
이 체크리스트는 의도적으로 간단합니다. 그 목적은 호환성을 사후 고려 사항이 아니라 일반적인 릴리스 프로세스의 일부로 만드는 것입니다.
결론
금융 API는 제품, 보고 요구 사항 및 다운스트림 시스템의 변화에 따라 진화합니다. 가장 안전한 접근 방식은 이러한 변경 사항을 명시적으로 만드는 것입니다.
계약(contract)을 정의하고, 구조적 변경과 의미론적 변경을 구별하며, 오래된 필드를 제거하기 전에 대체 필드를 도입하고, 실제 소비자의 동작을 테스트하세요.
스키마는 단순히 JSON의 설명이 아닙니다. 데이터를 생성하는 시스템과 그 데이터에 의존하는 시스템 간의 합의입니다.
이러한 합의가 소프트웨어 아키텍처의 일부로 취급될 때, 팀들은 모든 변경 사항을 프로덕션 사고(production incident)로 만들지 않고도 API를 발전시킬 수 있습니다.
AI 도움말 고지: 이 초안은 AI의 도움을 받아 작성되었습니다. 게시하기 전에 본인의 기술 경험으로 검토하고, 확인하며, 수정해야 하며, DEV Community의 현재 AI 지원 기사 가이드라인을 따라주십시오.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기