FCM 토큰이 저장 안 되던 잘못된 푸시 알림 엔드포인트 수정
목차
앱에서 FCM 토큰이 서버에 아예 저장이 안 되고 있었다. 원인은 단순했는데, 없는 엔드포인트를 호출하고 있었던 것.
배경: 존재하지 않는 엔드포인트를 조용히 호출하던 코드
PUT /device/me — 이 경로가 API 스펙에 없었다. 서버는 PATCH /device/{uuid} 만 열어놓고 있었고, 앱은 아무도 모르게 404를 받고 있었다. 더 문제였던 건 이게 "조용히" 실패하고 있었다는 점이다. FCM 토큰 갱신 실패가 로그나 얼럿으로 튀어오르지 않으니 한동안 아무도 인지를 못 했다.
결국 푸시 알림이 특정 기기에서 안 간다는 제보가 들어오고 나서야 추적이 시작됐다. 서버 로그에는 해당 기기의 토큰 갱신 이력이 없었고, 앱 코드를 열어보니 api_client.dart 에서 엔드포인트 경로가 틀려 있었다.
변경 파일이 app/lib/data/api_client.dart 단 하나라는 게 이번 수정의 성격을 잘 말해준다. 핀포인트 수정. 로직이 잘못된 게 아니라 경로 문자열 하나가 잘못된 것. 그렇기 때문에 오히려 더 오래 숨어있을 수 있는 종류의 버그다.
작업 내용: PUT → PATCH, /me → /{uuid}
변경의 핵심은 두 가지였다.
| 항목 | 수정 전 | 수정 후 |
|---|---|---|
| HTTP 메서드 | PUT |
PATCH |
| 경로 | /device/me |
/device/{uuid} |
// Before
await _dio.put('/device/me', data: {'fcmToken': token});
// After
await _dio.patch('/device/$uuid', data: {'fcmToken': token});
메서드도 달랐고 경로 패턴도 달랐다. /me 는 인증된 사용자 본인을 가리키는 편의 경로인데, 기기(device) 리소스는 그 패턴을 쓰지 않고 uuid를 직접 받는 구조였다. 아마 처음 이 코드를 작성할 때 user 관련 API 패턴을 그대로 가져다 쓴 것 같다. 그리고 서버 쪽 스펙 문서나 실제 구현이랑 한 번도 크로스체크가 안 된 채로 코드가 올라간 거다.
회고: API 클라이언트 코드는 "컴파일이 된다고 동작하는 게 아님"을 다시 확인
이런 버그는 사실 코드리뷰에서 잡기가 굉장히 까다롭다. 경로 문자열이 맞는지 틀린지는 서버 스펙을 알아야만 판단할 수 있는데, 리뷰어가 매번 API 스펙을 열어두고 대조하면서 리뷰하는 경우는 드물다.
팀 차원에서 이런 류의 버그를 줄이려면 몇 가지 접근이 있다.
- API 클라이언트 코드를 서버 스펙에서 자동 생성 (OpenAPI/Swagger 기반 codegen). 스펙이 소스가 되면 경로 불일치 자체가 불가능해진다.
- 통합 테스트나 mock 서버 테스트를 API 클라이언트 레이어에 붙여두기. 실제 경로를 호출해서 404/405 를 잡아내는 테스트가 하나라도 있었다면 배포 전에 발견됐을 것.
- FCM 토큰 갱신 실패를 에러 이벤트로 명시적으로 추적하기. 알림 기능처럼 사용자 경험에 직결되는 비동기 작업은 실패를 삼켜서는 안 된다.
이번 수정은 한 줄짜리 fix지만, 이 버그가 살아있던 동안 FCM 토큰이 갱신되지 않아서 푸시를 못 받은 기기가 얼마나 됐는지는 따로 추적이 필요하다. 데이터가 쌓이지 않은 거라 소급도 안 된다. 그게 가장 아픈 부분.
API 클라이언트는 앱과 서버 사이의 계약서다. 계약서에 오탈자가 있으면 서명 자체가 무효다. 이번에 다시 한번 그걸 실감했음.
다음
🛒 이 글과 어울리는 추천 상품
*위 링크는 쿠팡파트너스 활동의 일환이며, 일정액의 수수료를 제공받을 수 있습니다.
댓글 0
첫 댓글 달아줘.