개발 slecs

결제 잔액 복원 분기에서 텔레그램 알림 직접 호출 제거

목차

결제 출금 흐름에서 잔액 복원 실패 분기에 박혀 있던 텔레그램 알림 호출을 제거했다.

왜 이 코드가 거기 있었나

출금 처리 중 예외가 발생하면 차감됐던 charge_balance를 다시 복원하는 분기가 있다. 일종의 보상 트랜잭션 구간이다. 초기에 이 복원 로직을 짤 때 "복원마저 실패하면 우리가 즉시 알아야 한다"는 판단 하에 텔레그램 알림을 직접 호출하는 코드를 넣었을 거다. 당시엔 합리적인 선택이었다.

문제는 알림 채널이 비즈니스 로직 한복판에 직접 묻혀 있다는 것이다. pay/web 쪽 내부 클래스와 payment/web 쪽 내부 클래스, 두 군데에 동일한 패턴으로 텔레그램 호출이 박혀 있었다. 비슷한 흐름이 두 레이어에 나뉘어 있는 구조 자체도 정리가 필요한 신호였지만, 우선 이번엔 알림 호출 제거에 집중했다.

무엇이 문제였나

문제 설명
알림 채널 직접 의존 비즈니스 로직이 텔레그램 SDK/클라이언트에 직접 결합
복원 실패 처리 흐름 복잡화 알림 호출 자체가 예외를 던질 경우 복원 분기 흐름이 오염될 가능성
중복 호출 위험 동일 이벤트에 두 클래스에서 각각 알림이 발송될 수 있는 구조
테스트 어려움 알림 클라이언트 mock 없이는 해당 분기 단위 테스트가 번거로움

복원 실패 분기는 이미 예외 상황이다. 그 안에서 또 외부 I/O(텔레그램 API 호출)를 직접 치는 건 "예외 처리 중 또 다른 예외 가능성"을 열어두는 셈이다. 텔레그램 쪽이 일시적으로 응답이 느리거나 장애가 나면, 복원 분기 자체가 엉키는 상황이 발생할 수 있었다.

// 기존 패턴 (제거 전)
try {
    restoreChargeBalance(userId, amount);
} catch (Exception e) {
    log.error("charge_balance 복원 실패", e);
    telegramClient.sendAlert("복원 실패: " + e.getMessage()); // ← 이게 문제
    throw e;
}

// 변경 후
try {
    restoreChargeBalance(userId, amount);
} catch (Exception e) {
    log.error("charge_balance 복원 실패", e);
    throw e;
}

알림은 이 레이어에서 직접 쏠 게 아니라, 호출 스택 상위에서 또는 별도 이벤트/모니터링 파이프라인에서 처리하는 게 맞다.

리팩터링 판단 기준

이런 류의 "알림 호출 제거"는 얼핏 기능 약화처럼 보여서 팀 내에서 컨펌을 받을 때 설명이 필요하다. 내가 신경 쓴 포인트는 세 가지였다.

  • 알림이 사라지는 게 아니다 — 로그는 그대로 남고, 상위 레이어나 인프라 레벨 모니터링으로 탐지 가능하다
  • 이 코드가 두 클래스에 중복돼 있던 이상, 한쪽만 수정하면 불일치가 생긴다. 두 군데 동시에 제거하는 게 맞다
  • 복원 실패는 DB 레벨 이슈일 가능성이 높아서, 알림보다 트랜잭션 롤백/재시도 전략이 더 실질적인 대응이다

팀원한테 리뷰 줄 때 "알림 제거니까 위험하지 않아?"라는 질문이 나올 수 있다. 그 질문 자체는 맞는 감각이다. 단 "이 코드가 없어지면 모니터링 사각지대가 생기는가"를 정확히 따져봐야 한다. 이번 경우엔 로그와 상위 에러 핸들링이 이미 있었기 때문에 사각지대가 아니었다.

코드리뷰에서 이런 판단 근거를 PR 설명에 명시적으로 적어두는 게 중요하다. "그냥 지웠음"으로 올라오는 순간 리뷰어 입장에서 불안해지고, 불필요한 핑퐁이 생긴다.


paypayment 두 패키지가 비슷한 역할로 병존하는 구조는 다음 정리 대상이다. 이번 작업이 그쪽 실마리를 건드린 셈이기도 하다.

끝.

댓글 0

첫 댓글 달아줘.