실제 QA에서 발견한 정책 불일치 사례를 바탕으로 썼다. 서비스와 도메인은 일반화하고, 여러 API가 같은 사용자 정책을 표현할 때의 문제만 남겼다.
한 화면에서는 어떤 혜택을 5개월까지 받을 수 있다고 나왔다. 바로 옆 안내 화면에서는 4개월까지라고 했다.
둘 중 하나가 오래된 문구를 들고 있던 것도 아니고, 프론트엔드가 숫자를 잘못 계산한 것도 아니었다. 두 화면은 서로 다른 API를 사용했고, 각 API는 서로 다른 데이터 원천에서 같은 정책을 읽고 있었다.
프론트엔드만 보면 두 화면은 모두 정상 동작했다. 사용자에게는 둘 중 하나가 틀린 서비스였다.
TL;DR
- 같은 사용자 개념을 여러 API가 제공하면 화면 단위의 정상만으로는 충분하지 않다.
- 데이터 원천이 다르면 값뿐 아니라 갱신 시각과 적용 규칙도 달라질 수 있다.
- 프론트엔드가 임의로 한쪽을 정답으로 선택하면 불일치를 숨길 뿐 해결하지 못한다.
- 정책에는 소유자, 기준 원천, 버전 또는 적용 시각이 필요하다.
- QA와 계약 테스트는 API 하나가 아니라 사용자가 오가는 화면 사이의 일관성을 확인해야 한다.
각 화면은 맞았지만 서비스는 틀렸다
구조를 단순화하면 이랬다.
주문 화면
-> 주문 정책 API
-> 주문 데이터베이스
혜택 안내 화면
-> 프로모션 API
-> 프로모션 데이터베이스
주문 화면은 실제 거래에 사용하는 정책을 보여줬다. 안내 화면은 사용자에게 혜택을 설명하는 별도의 정책 데이터를 보여줬다. 둘 다 자신의 데이터 원천을 정확하게 렌더링했다.
문제는 사용자가 데이터 원천을 구분하지 않는다는 데 있다.
안내 화면: 4개월까지 가능
주문 화면: 5개월까지 가능
사용자는 “왜 화면마다 말이 다르지?”라고 묻는다. 어느 팀의 API인지, 어느 데이터베이스에서 왔는지는 아무 의미가 없다.
이런 문제는 기능 테스트로 놓치기 쉽다.
- API 응답 스키마가 맞다.
- 화면에 값이 정상적으로 표시된다.
- 버튼과 결제 동작도 문제없다.
- 각 시스템의 단위 테스트도 통과한다.
그래도 사용자 경험은 일관되지 않을 수 있다. 컴포넌트와 API의 로컬 정합성은 서비스 전체의 의미 정합성을 보장하지 않는다.
같은 단어가 같은 개념인지부터 확인해야 한다
두 API가 비슷한 필드 이름을 내려준다고 해서 반드시 같은 의미는 아니다.
{
"availableMonths": 5
}
한 API의 availableMonths는 실제 거래 승인 기준일 수 있다. 다른 API에서는 이벤트 안내를 위해 수동으로 입력한 최대 개월 수일 수 있다. 이름은 같아도 다음 조건이 다를 수 있다.
- 적용 대상
- 갱신 주기
- 기준 시각
- 예외 조건
- 데이터 소유자
- 지연 허용 범위
먼저 확인할 질문은 “왜 값이 다르지?”보다 다음에 가깝다.
이 두 값은 정말 같은 정책을 표현해야 하는가?
같은 정책이라면 불일치는 결함이다. 서로 다른 목적의 값이라면 화면에서 같은 말로 표현한 것이 문제다. 어느 쪽이든 프론트엔드에서 숫자만 맞추는 것으로 끝낼 수 없다.
기준 원천을 정하지 않으면 프론트엔드가 판정자가 된다
불일치를 발견하면 가장 쉬운 해결책은 한 화면이 다른 API를 사용하게 만드는 것이다.
안내 화면도 주문 정책 API를 사용한다
가능하다면 좋은 방향이다. 하지만 데이터 모양, 호출 시점, 트래픽, 권한, 장애 영향이 달라 바로 합칠 수 없는 경우가 많다.
그다음으로 쉬운 선택은 프론트엔드에서 두 값을 비교해 하나를 선택하는 것이다.
const displayMonths = Math.min(orderMonths, promotionMonths);
안전해 보이지만 새로운 정책을 프론트엔드가 만든다. 최솟값이 정답이라는 도메인 근거가 없다면, 불일치 원인을 숨기고 세 번째 규칙을 추가한 셈이다.
같은 정책을 여러 시스템이 제공해야 한다면 먼저 다음을 정해야 한다.
- 기준 원천: 실제 정책의 최종 판단자는 어느 시스템인가
- 파생 원천: 다른 시스템은 원본을 어떻게 복제하거나 가공하는가
- 동기화 지연: 값이 달라도 허용되는 시간은 얼마인가
- 정책 소유자: 값이 다를 때 누가 결정하는가
- 실패 동작: 기준 원천을 사용할 수 없을 때 무엇을 보여주는가
이 결정 없이 프론트엔드만 고치면 다음 정책 변경 때 같은 문제가 돌아온다.
값과 함께 버전 또는 기준 시각이 필요하다
서로 다른 데이터 원천을 당장 하나로 합칠 수 없다면, 적어도 같은 정책을 보고 있는지 확인할 단서가 필요하다.
{
"policyId": "installment-policy",
"policyVersion": "2026-07-21T02:00:00Z",
"effectiveFrom": "2026-07-21T00:00:00Z",
"availableMonths": 5
}
모든 API가 동일한 형식을 사용할 필요는 없다. 중요한 것은 운영 중에 다음 질문에 답할 수 있는 정보다.
- 두 화면이 같은 정책 버전을 사용했는가
- 한쪽의 동기화가 늦은 것인가
- 아직 적용 시각이 되지 않은 값인가
- 특정 예외 규칙 때문에 결과가 갈린 것인가
값만 로그에 남기면 “당시에는 4와 5가 달랐다”까지만 알 수 있다. 버전과 기준 시각이 있으면 어느 경로에서 갱신이 끊겼는지 좁힐 수 있다.
일관성 검증은 세 층으로 나눌 수 있다
1. 데이터 동기화 검증
기준 원천이 바뀌었을 때 파생 데이터가 정해진 시간 안에 따라오는지 확인한다.
기준 정책 변경
-> 복제 또는 이벤트 전달
-> 파생 저장소 갱신
-> 지연 시간과 실패 건수 기록
여기서는 메시지 전달 실패, 재시도, 오래된 데이터 수가 중요하다.
2. API 의미 계약 검증
스키마가 같다는 것만으로 부족하다. 같은 입력에서 의미상 같은 결과를 내야 하는 필드는 교차 검증할 수 있다.
expect(orderPolicy.availableMonths)
.toBe(promotionPolicy.availableMonths);
실제 계약에는 적용 대상과 시각을 함께 넣어야 한다. 모든 데이터를 매번 비교하기 어렵다면 대표 정책이나 변경 직후 표본부터 시작할 수 있다.
3. 사용자 여정 검증
사용자는 API가 아니라 화면을 이동한다.
혜택 안내 확인
-> 주문 화면 진입
-> 같은 조건의 정책 문구 비교
-> 실제 선택 가능 범위 확인
이 검증은 각 페이지의 E2E를 따로 실행하는 것과 다르다. 한 사용자가 연속해서 보는 정보가 같은지를 확인한다.
어제의 불일치도 이 층에서 발견됐다. QA가 한 화면만 확인했다면 두 API는 계속 각자 정상으로 남았을 것이다.
불일치를 발견했을 때의 대응 순서
값이 다르다는 제보를 받으면 프론트엔드 표시 코드부터 고치고 싶어진다. 하지만 다음 순서가 더 안전하다.
- 두 화면의 실제 응답과 조회 시각을 확보한다.
- 동일한 사용자·상품·정책 조건인지 확인한다.
- 각 필드의 의미와 기준 원천을 확인한다.
- 실제 거래에 사용하는 값과 안내용 값을 구분한다.
- 정책 소유자에게 기대 결과를 확정받는다.
- 수정 전까지 사용자·VOC·온콜에 필요한 차이를 공유한다.
- 데이터, API, 프론트엔드 중 어느 층에서 고칠지 결정한다.
- 두 화면을 연결한 회귀 테스트를 추가한다.
여기서 6번이 중요하다. 구조적인 수정에 시간이 걸려도 현재의 불일치와 실제 거래 기준을 공유하면 잘못된 안내를 줄일 수 있다. 원인을 아는 몇 사람의 머릿속에만 남겨두면 같은 질문이 다시 들어온다.
하나의 데이터베이스가 항상 정답은 아니다
이 문제의 결론을 “데이터베이스를 하나로 합치자”로 단순화할 수는 없다. 서로 다른 시스템이 각자의 가용성과 성능 요구를 위해 데이터를 복제하는 것은 자연스럽다.
문제는 복제 자체보다 다음이 없는 상태다.
- 무엇이 원본인지
- 얼마나 늦어도 되는지
- 어긋났을 때 어떻게 발견하는지
- 사용자에게 어느 값을 보여줄지
- 누가 복구할지
분산된 데이터는 관리할 수 있다. 출처와 계약이 불분명한 데이터는 관리하기 어렵다.
프론트엔드가 할 수 있는 일
프론트엔드가 데이터 구조 전체를 바로 바꾸지는 못해도 문제를 더 빨리 드러낼 수 있다.
- 응답의 정책 버전과 기준 시각을 진단 로그에 남긴다.
- 두 화면을 오가는 E2E 시나리오를 만든다.
- 사용자에게 같은 의미로 보이는 문구와 필드를 목록화한다.
- 값이 다를 때 임의로 보정하지 않고 관측 이벤트를 남긴다.
- 실제 거래 기준과 단순 안내 값을 코드와 문서에서 구분한다.
- 정책 변경 작업의 체크리스트에 관련 API와 화면을 함께 넣는다.
특히 “이 API를 수정했으니 끝”이라는 작업 경계를 경계해야 한다. 사용자 정책의 경계는 저장소나 팀 경계와 일치하지 않을 수 있다.
마치며
어제의 두 화면은 모두 자신에게 주어진 데이터를 정확하게 보여줬다. 그래서 코드만 보면 결함을 찾기 어려웠다.
하지만 사용자는 시스템 경계를 보지 않는다. 같은 혜택을 설명하는 두 화면이 다른 숫자를 보여주면 서비스가 약속을 지키지 않는다고 느낀다.
같은 정책을 여러 API가 제공한다면 다음 질문을 먼저 남겨야 한다.
어느 시스템이 정답이며, 다른 시스템이 어긋났을 때 우리는 어떻게 알아차리는가?
화면의 일관성은 CSS나 컴포넌트만의 문제가 아니다. 데이터 원천, 정책 소유권, 동기화 지연과 사용자 여정을 함께 다뤄야 비로소 같은 답을 보여줄 수 있다.