결제 모니터 관련 문서를 실제 구현에 맞게 동기화
목차
문서 작업이 코드 작업보다 저평가받는다고 항상 생각한다. 코드는 PR에 diff가 보이고 리뷰어가 라인 단위로 코멘트를 달지만, TODO.md 한 줄 고치는 건 "그냥 문서네"로 넘어가는 경우가 많다. 근데 pay-monitor처럼 결제 흐름을 다루는 도메인에서 문서가 실제 구현과 어긋나 있으면, 그건 단순히 불편한 게 아니라 장애 대응 시간이 길어지거나 잘못된 방향으로 통합 작업이 진행되는 실질적인 원인이 된다.
이번에 slecs 레포의 TODO.md를 업데이트한 건 딱 그 맥락이었다. 구현이 먼저 됐고, 문서가 뒤따라가는 흔한 패턴. 코드 먼저 작성하고 문서는 나중에 - 이 방식 자체가 나쁜 건 아닌데, 결제 도메인이다 보니 간격을 빨리 좁히는 게 맞다고 판단해서 구현 확정 직후 바로 동기화했다.
건드린 영역은 API 스펙, 아키텍처 설명, 개발 가이드라인 중 하나 이상이었고, 특히 Webhook 관련 내용과 결제 연동 가이드 쪽이 주요 대상이었다. 파일은 TODO.md 하나였지만 이 한 파일이 pay-monitor 도메인 전체 개발 흐름을 추적하는 기준점 역할을 하고 있어서, 여기가 outdated이면 다른 팀원들 작업 방향이 틀어지는 구조다.
왜 결제 도메인 문서는 더 빠르게 동기화해야 하나
결제 흐름은 외부 API와 맞물려 있다. Webhook 이벤트 스펙이 바뀌거나 외부 결제 게이트웨이가 필드를 추가하거나 제거하면, 우리 쪽 코드보다 문서가 먼저 틀려지는 순서로 일이 생긴다. 팀원이 오래된 문서를 보고 구현했는데 실제 Webhook payload 구조랑 맞지 않아 디버깅에 시간을 쏟는 상황 - 흔한 패턴이고, 원인을 파면 문서 동기화 누락인 경우가 많다.
결제 연동 가이드에서 버전 정보가 특히 중요한 이유도 이것 때문이다. 어떤 외부 API 스펙 기준으로 구현했는지 남겨두지 않으면, 나중에 스펙이 바뀌었을 때 우리 코드가 예전 스펙을 따르는 건지 새 스펙을 따르는 건지 트래킹이 안 된다. 코드만 봐서는 알 수 없다. 이번 업데이트에서도 버전 기준을 함께 명시한 건 그 이유다.
Webhook 쪽 문서가 유독 관리가 어려운 이유는 외부 이벤트 발생 조건과 payload 구조가 우리 코드가 아닌 외부 사업자 정책에 따라 결정되기 때문이다. 내부 코드는 우리가 컨트롤하지만 Webhook은 수신만 하는 입장이라, 명세 변경 공지를 모니터링하고 내부 문서에 반영하는 루틴이 없으면 금방 stale해진다. 그리고 stale한 Webhook 문서는 없는 문서보다 더 나쁠 수 있다 - 틀린 정보를 사실처럼 참고하게 만드니까.
문서 유형별로 업데이트 트리거와 버전 명시 필요 여부를 분류해두면 관리 기준이 생긴다:
| 문서 유형 | 업데이트 트리거 | 버전 명시 |
|---|---|---|
| API 스펙 | 엔드포인트·필드 변경 시 | 필요 |
| Webhook 가이드 | 외부 provider 스펙 변경 시 | 필요 |
| 아키텍처 설명 | 컴포넌트 구조 변경 시 | 선택 |
| 개발 가이드라인 | 팀 컨벤션 변경 시 | 불필요 |
pay-monitor 도메인은 이 표에서 위쪽 두 행이 핵심이다. 거기서 누락이 생기면 나머지가 잘 관리되어 있어도 소용없다.
로그는 증거고, 문서는 지도다
이번 작업에 로깅 관련 내용도 포함됐다. pay-monitor처럼 결제 처리 흐름을 추적해야 하는 영역에서 로그는 단순한 디버깅 도구를 넘어 사후 추적의 근거가 된다. 어떤 Webhook이 언제 들어왔고, 어떤 처리 경로를 탔고, 어디서 실패했는지 - 로그 없이 재현하려면 결국 코드 전체를 다시 읽어야 하는 상황이 반복된다.
근데 로그가 과하면 그것도 문제다. INFO 레벨로 Webhook 수신마다 전부 찍어두면 트래픽이 많을 때 로그 스토리지가 빠르게 차고, 정작 찾아야 할 에러 로그가 노이즈에 묻힌다. 기준은 단순하게 잡는다: "이 로그 없이 장애가 났을 때 원인 파악에 얼마나 더 걸리나?" 그 답이 의미 있는 시간이면 남기고, 아니면 안 남긴다.
결제 처리 흐름에서 최소한 남겨야 할 포인트를 패턴으로 정리하면 대략 이렇다:
# Webhook 수신 - 외부 이벤트 진입점
logger.info("webhook_received",
event_type=payload["type"],
event_id=payload["id"])
# 결제 상태 전이 - 핵심 비즈니스 이벤트
logger.info("payment_status_changed",
payment_id=payment_id,
from_status=prev_status,
to_status=new_status)
# 처리 실패 - ERROR 레벨, 재시도 가능 여부 포함
logger.error("payment_processing_failed",
payment_id=payment_id,
error=str(e),
retryable=is_retryable(e))
레벨은 INFO/ERROR 두 개로 대부분의 경우를 커버한다. WARNING은 "이상하긴 한데 처리는 됐다"는 애매한 케이스에 쓰는데, pay-monitor 같은 결제 도메인에서 그 애매함이 나중에 조용한 버그로 이어지는 경우가 있어서 WARNING보다는 명확히 ERROR를 쓰거나 아예 찍지 않는 쪽이 낫다고 본다. 처리 결과가 명확할 때 로그 레벨도 명확해진다.
구현과 문서를 같은 PR에 담는 것에 대해
이번처럼 구현 후에 문서를 곧바로 따라잡는 것도 나쁘진 않다. 이상적으로는 구현과 문서 업데이트가 같은 PR에 들어가는 형태인데, 실제로 지켜지지 않는 이유는 코드 작성하면서 문서까지 동시에 챙기기가 번거롭기 때문이다. 구현 중간에 스펙이 바뀌기도 하고, 확정되기 전에 문서에 써두면 결국 두 번 쓰게 되는 상황도 생긴다.
그 번거로움을 줄이는 방법 중 하나는 문서 형식을 가능한 한 단순하게 유지하는 것이다. TODO.md처럼 가볍게 관리할 수 있는 포맷이 정교한 위키 페이지보다 오히려 더 자주 갱신된다. 코드 옆에 있는 마크다운 파일이 현실에서 더 살아있는 경우가 많다 - 작성 비용이 낮아야 유지 비용도 낮다.
외부 인터페이스 - Webhook 핸들러, API 엔드포인트, 결제 이벤트 타입 - 를 건드리는 변경에 한해서는 문서 없는 merge를 막는 게 장기적으로 훨씬 낫다고 생각한다. 강제하기 어려운 관행이긴 하지만, pay-monitor처럼 결제가 얽힌 도메인에서는 그 비용이 나중에 어떻게든 청구된다. 이번 동기화 작업은 그 청구서를 미리 처리한 셈이고, 이후 코드가 바뀌면 문서도 같이 따라가는 루틴을 계속 유지할 예정이다.
댓글 0
첫 댓글 달아줘.