결제 알림을 텔레그램·디스코드 이중화하고 비즈니스 로직에서 분리
목차
결제 알림이 텔레그램 봇 토큰 만료 한 번에 전부 막히는 구조는 오래 전부터 부채였다. 단일 채널 의존도가 높다는 걸 알면서도 "아직 멀쩡하니까" 미뤘는데, 이번에 디스코드 웹훅을 붙이면서 기회 삼아 알림 레이어 전체를 뜯어고쳤다.
채널을 고를 때 따진 것들
후보는 슬랙·디스코드·이메일이었다. 팀 사이즈나 워크플로에 따라 답이 다르지만, 지금 상황에서 기준은 세 가지였다 - 인증 복잡도, 포맷 표현력, 모바일-데스크톱 커버 범위.
| 항목 | 텔레그램 | 디스코드 | 슬랙 |
|---|---|---|---|
| 인증 부담 | 봇 토큰 + chat_id | 웹훅 URL 1개 | 워크스페이스 토큰 |
| 메시지 포맷 | 마크다운 제한적 | Embed 풍부 | Block Kit 무거움 |
| 운영 친화도 | 모바일 푸시 강함 | 데스크톱 알림 강함 | 채널 정리 편함 |
디스코드 웹훅은 URL 하나로 끝난다. Embed를 쓰면 색상, 필드, 타임스탬프를 구조화할 수 있어서 충전 성공/실패/지연을 시각적으로 구분하기 좋다. 슬랙 Block Kit도 표현력은 비슷한데 JSON이 너무 장황하고, 이메일은 실시간 알림 용도로는 맞지 않았다. 결국 텔레그램은 모바일 쪽 담당, 디스코드는 데스크톱 쪽 담당으로 역할을 쪼갰다.
구현하면서 진짜로 막혔던 지점
채널 추가 자체는 30분이면 끝난다. 오래 걸린 건 기존 코드의 냄새를 같이 제거하는 과정이었다.
동기 호출 문제. 처음엔 그냥 텔레그램 호출 아래에 디스코드 호출을 줄줄이 붙였다. 테스트하다 보니 외부 API 두 곳을 순서대로 기다리면서 결제 콜백 응답이 늦어지는 게 눈에 보였다. 알림 실패가 결제 트랜잭션 실패로 이어지면 안 되니, 알림 전체를 비동기 큐로 빼고 본 로직과 완전히 분리했다. 알림이 죽어도 결제 흐름엔 영향 없는 구조.
메시지 빌더 분기. 동일 이벤트를 두 채널로 보내다 보니 채널마다 포맷팅이 달라서 if/else가 중간에 끼어들었다. 공통 DTO 하나를 정의하고, 각 채널 어댑터가 그걸 받아서 자기 포맷으로 변환하게 바꿨다. 나중에 카카오워크나 이메일을 추가할 때도 어댑터 하나만 구현하면 되는 구조.
[입금 이벤트] → publish → [알림 라우터]
├─ 텔레그램 어댑터 (MarkdownV2 변환)
└─ 디스코드 어댑터 (Embed JSON 변환)
비즈니스 로직 오염. 가장 큰 문제는 따로 있었다. 입금 처리 핸들러 안에 결제대행사 응답 파싱 코드와 알림 호출 코드가 섞여있었다. 파싱 로직을 고치려다가 알림 관련 분기를 같이 건드려야 하는 상황이 반복됐다. 이번에 이벤트 발행 → 리스너 구조로 빼내면서 핸들러는 파싱과 상태 갱신에만 집중하게 됐다. 알림은 이벤트 수신 후 별도 레이어에서 처리.
텔레그램 기존 코드 정리. 이왕 손대는 김에 오래된 냄새들도 같이 제거했다.
- 코드에 하드코딩돼 있던 봇 토큰 → 환경변수 통일
- 재시도 로직이 사실상 무한루프 가능성이 있었음 → 지수 백오프 + 최대 3회 캡
- 메시지 길이가 4096자 넘으면 잘리던 버그 → 청크 분할 후 순차 전송
재시도 캡을 걸 때 한 가지 고민이 있었다. 3회 실패 후 조용히 버리면 운영팀이 놓칠 수 있으니, 실패 자체를 별도 에러 로그 채널에 기록하도록 했다. 알림 실패를 알리는 알림. 좀 우습지만 실제로 유용하다.
분리가 진짜 수확이었음
채널 이중화는 명목상 목표였고, 실제로 얻은 건 알림 레이어의 독립이었다. 비즈니스 로직과 알림이 결합되면 두 가지 문제가 생긴다 - 테스트할 때 외부 API를 모킹해야 하고, 채널을 추가하거나 바꿀 때 로직 코드를 건드려야 한다. 둘 다 나중에 반드시 사고 친다.
이벤트 기반으로 빼내면 반대가 된다. 핸들러 테스트에서 알림은 신경 안 써도 되고, 새 채널은 리스너 하나 추가로 끝난다. 어댑터 패턴과 결합하면 기존 코드 변경 없이 확장이 가능해진다 - 교과서에서 읽던 OCP가 실제로 체감되는 순간이다.
운영팀 쪽 피드백은 단 한 줄이었다. "텔레그램 안 와도 디스코드 보고 알아챘어요." 그걸로 충분했다. 중복 알림이 오히려 피로하다는 의견이 나올 줄 알았는데, 채널별 역할이 나뉘어져 있어서인지 그런 반응은 없었다. 다음에 채널 하나를 더 추가해야 할 때는 어댑터만 작성하면 된다. 구조가 준비됐으니 그때는 진짜로 30분짜리 작업이 될 거다.
댓글 0
첫 댓글 달아줘.