MSA 환경에서 FeignClient 응답 데이터가 누락된 이유는? – CommonResponse<DTO>와 변수 범위 이슈
1. 문제발생
이번에 개발한 기능은 Order-Service에서 주문 생성 시 Stock-Service와 연동하여 재고를 차감하고 이후 Delivery-Service에 배송 생성을 요청하는 전체 흐름이다.
이 과정에서 예상치 못한 문제가 발생했다. 분명 Stock-Service에서 hubId, price를 잘 반환하고 있는데도 OrderService에서 해당 값이 null, 0으로 들어가는 버그가 발생한 것이다. 디버깅 로그를 찍어보면 다음과 같았다.
INFO OrderService - stockResponse.hubId = null
INFO OrderService - totalPrice = 0
하지만 DB에서는 정상적으로 hubId와 price가 존재하는 것을 확인할 수 있었다. 그럼 대체 문제는 어디에 있었을까?
2. 구조 요약
Order-Service → Stock-Service
- FeignClient를 이용해 checkAndDecreaseStock() 호출
- StockService는 다음과 같은 DTO를 반환한다.
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class StockCheckResponse {
private UUID productId;
private UUID hubId;
private int price;
}
이 응답을 CommonResponse로 감싸서 다음과 같이 반환
return CommonResponse.OK(stockCheckResponse, "성공");
3. 문제 상황
[초기 OrderService의 코드]
StockCheckResponse stockResponse = null;
try {
CommonResponse<StockCheckResponse> response = stockClient.checkAndDecreaseStock(request);
StockCheckResponse stockResponse = response.getData(); // X 여기서 stockResponse는 새로운 지역변수
...
} catch (Exception e) {
...
}
바로 이 부분! stockResponse를 새로운 지역 변수로 다시 선언(StockCheckResponse stockResponse = ...)하면서 클래스 최상단에 있던 stockResponse는 여전히 null 상태로 남게 된다.
결국 이후에 사용하는 값은 null → hubId = null, totalPrice = 0으로 저장되는 것이다.
4. 해결 방법
해결은 간단했다. 지역 변수로 다시 선언하지 않고 이미 선언된 stockResponse에 대입만 하면 된다.
[수정된 코드]
StockCheckResponse stockResponse = null;
try {
CommonResponse<StockCheckResponse> response = stockClient.checkAndDecreaseStock(stockRequest);
stockResponse = response.getData(); // 선언 없이 대입만! 메서드 스코프 변수에 제대로 할당
} catch (Exception e) {
...
}
이제 stockResponse.getHubId()와 stockResponse.getPrice()가 정상적으로 값을 가진다.
5. FeignClient에서 CommonResponse<DTO>를 써야 할까?
이번 이슈를 통해 알 수 있었던 또 하나의 교훈은 FeignClient에서 DTO를 감싸는 방식의 통일이다.
@FeignClient(name = "stock-service", url = "http://localhost:8082")
public interface StockClient {
@PutMapping("/api/stocks/check")
CommonResponse<StockCheckResponse> checkAndDecreaseStock(@RequestBody DecreaseStockRequest request);
}
Stock-Service는 항상 응답을 CommonResponse<T>로 감싸도록 구성되어 있기 때문에 FeignClient에서도 이를 반영해야 한다. 만약 StockCheckResponse만 받으려고 하면 직렬화 오류나 null 값 이슈가 발생할 수 있다.
6. 마무리
- Java에서 같은 이름의 변수를 블록 안에서 다시 선언하면 바깥 변수와는 전혀 다른 메모리 공간에 할당된다. → 주의!
- MSA에서 서비스 간 통신은 응답 구조를 동일하게 맞추는 것이 굉장히 중요하다.
- FeignClient의 응답을 CommonResponse<T>로 감싸는 구조라면, 모든 서비스에서 이를 표준처럼 맞추자.
- 데이터가 null로 들어오면 바로 의심할 부분은
- JSON 응답 구조와 DTO 매핑이 일치하는가?
- 변수 범위가 꼬인 것은 아닌가?
- FeignClient가 올바른 타입으로 받고 있는가?
이번 이슈는 단순한 변수 스코프 문제였지만 MSA 환경에서의 연동과 응답 표준화가 얼마나 중요한지를 다시 느끼게 해줬다.