공급 체인 환불 검증 로직 추가
목차
공급 체인 환불 검증 로직 추가가 생각보다 손이 많이 갔다. 파일 수만 보면 백엔드 1개, 쿼리 XML 1개라서 작아 보이는데, 실제론 환불 가능 여부를 판단하는 분기가 여러 조건을 물고 늘어지는 구조라 영향 범위가 꽤 넓었음.
공급 체인에서 환불 검증은 단순 "환불 가능/불가능" 플래그 하나로 끝나는 문제가 아니다. 발주 상태, 입고 처리 여부, 정산 단계 등 선행 조건들이 서로 맞물려 있고, 그 조합마다 허용해야 할 케이스와 막아야 할 케이스가 달라진다. 이걸 여러 곳에서 제각각 검사하고 있었던 게 기존 코드의 문제였음. 중복 검증 코드가 흩어져 있으면 하나 고쳤을 때 다른 곳이 빠져나가는 구멍이 생기기 쉽다.
핵심 변경 내용
이번 작업에서 건드린 레이어는 두 곳이다.
| 레이어 | 파일 수 | 주요 변경 |
|---|---|---|
| 백엔드 로직 | 1개 | 환불 가능 여부 검증 로직 단일화 |
| 쿼리 (XML) | 1개 | 검증에 필요한 공급 체인 상태값 조회 최적화 |
백엔드 쪽에서 한 일은 결국 "조건 판단을 한 함수로 모으는 것"이었다. 기존엔 서비스 레이어 여기저기서 비슷한 체크를 반복하고 있었는데, 그 로직들을 뜯어보면 미묘하게 다른 부분이 있었음. 동일한 케이스를 서로 다른 기준으로 판단하면 어떤 경로로 들어오느냐에 따라 결과가 달라질 수 있다. 이건 버그가 재현이 안 된다는 소리고, 운영에서 제일 무서운 패턴이다.
XML 쿼리 쪽은 상태값을 가져오는 조인 구조를 정리했다. 기존 쿼리가 불필요한 서브쿼리를 끼고 있어서 검증에 필요한 컬럼만 정확히 가져오도록 다듬었음.
검증 로직을 단일화할 때 주의할 것
이런 류의 리팩터링에서 흔히 하는 실수가 "다 모아놨으니 이제 완벽하다"고 안심하는 거다. 실제로는 기존에 분산돼 있던 로직 각각이 조금씩 다른 케이스를 처리하고 있을 수 있어서, 단순히 하나를 기준으로 나머지를 없애면 엣지 케이스가 날아간다.
이번 작업에서 실제로 마주친 패턴을 일반화하면 이렇다.
// 기존 패턴 - 서비스 곳곳에 흩어진 검증
// ServiceA.java
if (order.getStatus() == OrderStatus.CONFIRMED) {
// 환불 처리...
}
// ServiceB.java
if (order.getStatus() == OrderStatus.CONFIRMED
&& supply.isSettled() == false) {
// 환불 처리...
}
// 단일화 후 - 검증 조건을 한 곳에서 관리
public boolean isRefundable(Order order, Supply supply) {
if (order.getStatus() != OrderStatus.CONFIRMED) {
return false;
}
if (supply.isSettled()) {
return false;
}
// 추가 조건은 여기서만 관리
return true;
}
ServiceB에만 있던 isSettled() 체크가 ServiceA에는 없었던 것처럼, 기존 코드를 모두 펼쳐놓고 합집합으로 조건을 확정하는 과정이 필요하다. 하나씩 지워가면서 "이 조건이 없어져도 되나?"를 물어봐야 한다.
에러 메시지도 이번에 정리했다. 환불이 왜 안 되는지 알 수 없는 메시지보다는 어떤 조건에서 막혔는지 로그에 남기는 게 낫다. 새벽에 이슈가 터졌을 때 로그 파악에 걸리는 시간이 진짜 다르다.
if (supply.isSettled()) {
log.warn("환불 불가 - 정산 완료 상태. orderId={}, supplyId={}",
order.getId(), supply.getId());
return false;
}
이 로그 한 줄 차이가 나중에 디버깅 시간을 30분은 줄여준다. 과장이 아님.
작업 회고
당장 기능이 추가된 것도 아니고 화면이 바뀐 것도 아니라서, 배포 나간다고 누군가 박수 치는 작업은 아니다. 그런데 이런 작업들이 안 쌓이면 나중에 진짜 기능 개발할 때 속도가 안 나온다. 검증 로직 하나 추가하려는데 "이거 건드리면 저기가 터질 것 같아서"라는 말이 나오기 시작하면 이미 늦은 거다.
이번 작업 하면서 다시 생각한 기준들:
- 6개월 후의 나: 이 코드를 다시 볼 때 왜 이렇게 짰는지 바로 이해할 수 있는가
- 다음 개발자: 공급 체인 도메인을 처음 보는 사람이 이 함수 하나만 보고도 환불 조건을 파악할 수 있는가
- 새벽 3시: 장애 상황에서 이 로직이 문제를 빠르게 좁혀주는가
| 좋은 코드 | 나쁜 코드의 신호 |
|---|---|
| 읽으면 의도가 바로 보임 | 주석 없으면 이해 불가 |
| 검증 조건이 한 곳에 모여 있음 | 같은 조건이 여러 파일에 흩어짐 |
| 변경이 한 곳에만 영향 | 한 줄 바꾸면 세 군데 따라감 |
| 테스트 작성이 자연스러움 | 테스트하려면 구조 먼저 바꿔야 함 |
변경 범위가 작아도 다음 사람이 헷갈리지 않게 만드는 것, 그게 이번 작업의 실질적인 목표였음. 코드 줄 수보다 그 다음 변경이 얼마나 쉬워졌느냐가 기준이다.
댓글 0
첫 댓글 달아줘.