개발 slecs

결제대행사 은행별 핸들러를 추상 계층으로 통합해 오류 분류 일원화

목차

결제대행사 연동은 "일단 돌아가면 건드리지 않는" 영역으로 오래 묵히기 쉽다. 은행별로 SDK가 달라서 각자 핸들러를 만들면, 처음엔 그게 제일 빠르다. 문제는 그 핸들러가 여섯 개쯤 쌓이고 나면 공통 로직이 여섯 벌 복붙된 상태가 된다는 것.

이번 케이스가 딱 그랬다. 에러 코드 매핑이 핸들러마다 따로 있고, 같은 "타임아웃" 상황인데 어떤 핸들러는 재시도, 어떤 핸들러는 즉시 실패로 박아놨다. 운영 이슈가 올라올 때마다 "이 은행은 왜 또 다르냐"를 해명하는 루틴이 생겼고, 더는 못 참겠어서 추상 계층을 다시 깎았다.

뭘 올리고 뭘 남길지

추상화할 때 제일 쉽게 빠지는 함정이 "공통처럼 보이는 것을 다 올리는 것"이다. 그러면 추상 계층이 비대해지고, 결국 은행별 구현체가 추상 계층 구현의 절반을 무시하거나 비워두게 된다. 이번엔 반대로 갔다. 은행별로 진짜 달라야 하는 것만 추상 메서드로 남기고, 나머지는 기본 구현을 추상 클래스 안에 박았다.

공통화 가능한 것을 추리다 보니 의외로 많이 나왔다.

  • 요청/응답 로깅 포맷
  • 타임아웃·연결 끊김·잔액 부족 같은 표준 예외 분류
  • 재시도 가능 여부 판단
  • 결과 코드를 내부 도메인 코드로 매핑하는 골격

최종적으로 추상 메서드 시그니처는 세 개로 정리됐다.

parseResponse(raw)        // 은행별 구현 필수
classifyError(parsed)     // 추상에서 기본 제공, 필요시 override
isRetryable(category)     // 추상에서만 정의, override 불가

isRetryable을 sealed로 막은 이유가 있다. 재시도 가능 판단은 멱등성 키 정책이랑 짝을 이뤄야 하는데, 은행별로 이걸 다르게 열어두면 멱등성 보장이 무너진다. 나중에 멱등성 키 정책을 손보면서 이 결정이 맞았다는 걸 확인했다. 아, 그건 별도 티켓으로 분리됐다. 추상화 작업 중에 건드리기엔 범위가 너무 컸다.

AI를 어디에 끼울까

에러 분류에 LLM을 쓰자는 아이디어가 초반에 있었다. 은행마다 에러 메시지 형식이 제각각이라 정규식으로 다 잡기 어려운 케이스가 분명히 있었기 때문이다. 그런데 막상 붙여보니 비용·지연·재현성 셋 다 맞지 않았다. 동일한 에러 문자열에 AI가 매번 다른 카테고리를 뱉는 순간, 이건 분류기로 쓸 수 없다는 게 명확해졌다.

결국 결정 트리가 우선, AI는 fallback으로 정착했다.

단계 처리 방식 비고
1 코드/메시지 정규식 매칭 대부분 여기서 끝
2 룰 기반 카테고리 분류 재시도 여부 결정
3 AI 분류 호출 unknown만 넘어옴
4 AI 결과를 룰로 흡수 다음부터 1단계에서 잡힘

4번이 핵심이다. AI가 분류한 결과를 그냥 쓰면 같은 패턴이 들어올 때마다 계속 AI를 호출해야 한다. 대신 분류 결과를 정규식 룰로 환원해서 저장했다. 하루가 지나니 AI 호출이 거의 일어나지 않았다. 이게 제대로 작동하려면 AI 응답 포맷이 일관돼야 하는데, 처음엔 카테고리 외 텍스트가 섞여 들어와서 enum 변환이 깨졌다. JSON 강제에 화이트리스트 검증까지 두 단계 걸었다.

삽질한 것들

추상 메서드 default 구현을 너무 친절하게 짰다가 한 번 크게 당했다. 특정 은행 핸들러가 override를 안 한 채로 배포됐는데, default 구현이 그냥 "성공"처럼 작동해버렸다. 실제 응답 처리가 skip되는 거니까 조용한 버그였다. 발견하고 나서 default를 throw UnsupportedOperationException으로 바꿨다. 불편해 보이지만, 구현자가 명시적으로 결정을 내리게 강제하는 게 훨씬 낫다.

템플릿 메서드 패턴을 쓸 때 default를 얼마나 "관대하게" 둘지는 항상 트레이드오프다. 관대할수록 빠르게 붙이기 쉽고, 엄격할수록 실수가 배포까지 가지 않는다. 결제처럼 오류 하나가 바로 돈 문제로 연결되는 도메인에서는 엄격한 쪽이 맞다.

재시도 가능 판단을 추상 계층에 올리는 것도 생각보다 파급이 컸다. 멱등성 키 정책을 같이 손봐야 한다는 걸 중간에 깨달았는데, 여기서 욕심 부리면 범위가 걷잡을 수 없어진다. 별도 티켓으로 쪼갰다. 추상화 작업과 멱등성 정책 변경을 한 PR에 넣으면 리뷰도 힘들고, 뭔가 터졌을 때 원인 파악도 어렵다.

핸들러별 코드 라인 수는 평균 40% 줄었고, "이 은행만 왜" 류 운영 이슈는 작업 후 일주일째 안 올라오고 있다. 남은 것은 AI fallback 비용 모니터링이다. 지금은 임시 그래프 수준이라, 룰 흡수율이 떨어지는 구간을 실시간으로 못 잡는다. 흡수가 잘 되고 있는 동안은 괜찮지만, 새 은행 연동이 추가되거나 기존 은행 에러 포맷이 바뀌면 AI 호출량이 다시 튀어오를 수 있다. 다음 스프린트에서 제대로 붙일 예정.

댓글 0

첫 댓글 달아줘.