전에 RestTemplate으로 연동했던 Gemini api 포스팅에 이어서 리팩토링 기록을 적어보려고 한다.
먼저, FeignClient로 변경하면 어떤 점이 좋은지를 알아보자면 아래와 같다.
1. 코드 가독성과 간결성
- 더 간단한 코드: FeignClient는 REST 클라이언트 인터페이스를 선언형으로 정의할 수 있어 코드가 간결하고 가독성이 좋습니다. HTTP 요청을 수동으로 구성할 필요가 없어 RestTemplate보다 직관적입니다.
- 직접 매핑: 인터페이스에 어노테이션을 통해 HTTP 메서드 및 엔드포인트를 명시함으로써 코드의 의도를 쉽게 파악할 수 있습니다.
2. 자동화된 HTTP 요청 처리
- 부가 작업 감소: FeignClient는 자동으로 HTTP 요청을 처리하고, 복잡한 설정이나 객체 매핑을 간단하게 수행할 수 있습니다. RestTemplate에서는 수동으로 객체를 매핑하거나 설정을 추가해야 하는 경우가 많습니다.
- 헤더 설정의 단순화: 인증이나 공통 헤더 추가와 같은 기능도 Feign 인터셉터를 통해 쉽게 구현할 수 있습니다.
3. 마이크로서비스 아키텍처(MSA)와의 시너지
- Spring Cloud 통합: FeignClient는 Spring Cloud와 완벽하게 통합됩니다. 특히, 서비스 디스커버리(e.g., Eureka), 로드 밸런싱(e.g., Ribbon), 회로 차단기(e.g., Hystrix) 등과 손쉽게 연동됩니다.
- 로깅 및 디버깅: FeignClient는 설정을 통해 로깅을 활성화할 수 있으며, 요청 및 응답을 쉽게 추적할 수 있어 디버깅에 유리합니다.
- 유연한 설정: application.yml 또는 application.properties에서 Feign과 관련된 설정을 통해 타임아웃, 재시도 정책 등을 쉽게 관리할 수 있습니다.
4. 확장성과 유연성
- 플러그인 가능: Feign은 다양한 플러그인과 연동할 수 있어, 사용자 정의 디코더, 인코더, 인터셉터 등을 쉽게 추가할 수 있습니다.
- 애노테이션 기반 확장: 커스텀 애노테이션을 사용해 공통 로직을 쉽게 재사용할 수 있습니다.
5. 에러 핸들링 및 복구
- 회로 차단기 통합: FeignClient는 Resilience4j 또는 Hystrix 같은 라이브러리와 연동해 서비스 장애 시 회로 차단기 패턴을 적용할 수 있습니다. 이는 MSA 환경에서 서비스 복원력에 큰 도움이 됩니다.
- Failover 처리: 다양한 재시도 전략과 예외 핸들링을 구성해 서비스 안정성을 높일 수 있습니다.
MSA에서의 이점
- 서비스 호출의 일관성: MSA에서 여러 마이크로서비스 간의 통신이 빈번한데, FeignClient는 일관성 있는 API 호출 방식을 제공하여 팀 간 협업과 유지보수성을 향상시킵니다.
- 로드 밸런싱: Feign은 Ribbon과 함께 기본적인 클라이언트 측 로드 밸런싱 기능을 제공합니다. 이를 통해 서비스 인스턴스 간의 부하를 분산시켜 성능을 최적화할 수 있습니다.
- 서비스 디스커버리: Eureka 같은 서비스 레지스트리와 통합하여 자동으로 다른 서비스의 위치를 탐색하고 호출할 수 있습니다.
요약
FeignClient는 RestTemplate보다 선언형 접근 방식을 통해 코드를 간결하게 만들며, Spring Cloud 생태계와의 통합성을 높여 MSA에서 특히 유용합니다. 자동화된 설정과 다양한 기능 지원 덕분에 마이크로서비스 간의 통신이 보다 효율적이고 안정적으로 이루어집니다.
위 정리는 검색을 통해 알아낸 정보들이다. 그렇지만 사실 나는 새로운 기술 스택 공부와 더 나은 가독성, 그리고 다음 프로젝트에서는 MSA를 사용하여 개발할 예정인데, MSA에서는 위에 적어놓은 이유들을 바탕으로 RestTemplate과의 확연한 차이를 보여서이다.
그러면 이제 RestTemplate에서 FeignClient로 변경하면서 바뀐 코드, 그리고 추가된 코드들을 기록하겠다.
첫 번째로, FeignClient 의존성을 추가해준다. - build.gradle
// feign-client
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign:4.1.2'
그리고 FeignClient 설정을 위해 @EnableFeignClients 어노테이션을 추가해줘야 하는데,
복잡한 설정이 필요없이 위 어노테이션만 필요한 상황이라면 맨 앞단의 Application의 상단에 붙여주면 된다.
@SpringBootApplication
@EnableFeignClients // 이곳에 추가
public class YourApplication {
public static void main(String[] args) {
SpringApplication.run(YourApplication.class, args);
}
}
GeminiFeignClient를 생성해준다.
@FeignClient(value = "geminiClient", url = "${gemini.api.url}")
public interface GeminiFeignClient {
@PostMapping
ChatResponse createChat(@RequestParam("key") String apiKey, @RequestBody ChatRequest chatRequest);
}
이전 글을 참고하면 알겠지만, 미리 만들어둔 ChatRequest에 맞춰서 요청하고, ChatResponse 응답값에 맞춰서 응답을 받아와준다.
Gemini api pro 연동 기록 - RestTemplate
이번 프로젝트에서 음식점 업주가 우리 서비스에 매장 상품을 등록할 때,상품 설명을 기입하는 과정에서 ai를 사용할 수 있게 연동을 해보려고 한다! 우선 아래 링크에서 구글 api 키를
hyunco.tistory.com
그리고 마지막 Service 코드 수정인데, 아래는 이전 RestTemplate을 사용한 서비스 로직이다.
@Service
@RequiredArgsConstructor
public class AIService {
private final AIJpaRepository aiJpaRepository;
private final StoreJpaRepository storeJpaRepository;
private final RestTemplate restTemplate;
@Value("${gemini.api.url}")
private String apiUrl;
@Value("${gemini.api.key}")
private String apiKey;
private static final String STORE_NAME_PROMPT = "우리 매장의 이름은 ";
private static final String PRODUCT_DESCRIPTION_PROMPT = " 입니다. 배달 서비스에 상품 등록을 하려고 하는데, 내가 다음 문장에 주는 정보들을 이용해서 상품 설명 작성을 도와줘.";
private static final String MAX_LENGTH_HINT = "답변은 최대한 간결하게 50자 이하로 작성해줘.";
public AIResponse createProductDescription(String storeId, AIRequest request, User user) {
validateOwner(user.getUserRole());
Store store = storeJpaRepository.findById(UUID.fromString(storeId))
.orElseThrow(() -> new IllegalArgumentException(ErrorCode.NOT_FOUND_STORE.getMessage()));
String requestText =
STORE_NAME_PROMPT + store.getName() + PRODUCT_DESCRIPTION_PROMPT + request.text() + MAX_LENGTH_HINT;
String requestUrl = apiUrl + "?key=" + apiKey;
ChatRequest chatRequest = new ChatRequest(requestText);
ChatResponse chatResponse = restTemplate.postForObject(requestUrl, chatRequest, ChatResponse.class);
if (chatResponse == null) {
throw new IllegalStateException(ErrorCode.INTERNAL_SERVER_ERROR.getMessage());
}
String message = chatResponse.candidates().get(0).content().parts().get(0).text();
aiJpaRepository.save(AIRequestLog.create(store, request.text(), message, user.getId()));
return new AIResponse(message);
}
}
그리고 바뀐 코드를 보자면
@Service
@RequiredArgsConstructor
public class AIService {
private final AIJpaRepository aiJpaRepository;
private final StoreJpaRepository storeJpaRepository;
private final GeminiFeignClient geminiFeignClient;
@Value("${gemini.api.key}")
private String apiKey;
private static final String STORE_NAME_PROMPT = "우리 매장의 이름은 ";
private static final String PRODUCT_DESCRIPTION_PROMPT = " 입니다. 배달 서비스에 상품 등록을 하려고 하는데, 내가 다음 문장에 주는 정보들을 이용해서 상품 설명 작성을 도와줘.";
private static final String MAX_LENGTH_HINT = "답변은 최대한 간결하게 50자 이하로 작성해줘.";
public AIResponse createProductDescription(String storeId, AIRequest request, User user) {
validateOwner(user.getUserRole());
Store store = storeJpaRepository.findById(UUID.fromString(storeId))
.orElseThrow(() -> new IllegalArgumentException(ErrorCode.NOT_FOUND_STORE.getMessage()));
String requestText =
STORE_NAME_PROMPT + store.getName() + PRODUCT_DESCRIPTION_PROMPT + request.text() + MAX_LENGTH_HINT;
ChatResponse chatResponse = geminiFeignClient.createChat(apiKey, new ChatRequest(requestText));
if (chatResponse == null)
throw new IllegalStateException(ErrorCode.INTERNAL_SERVER_ERROR.getMessage());
String message = chatResponse.candidates().get(0).content().parts().get(0).text();
aiJpaRepository.save(AIRequestLog.create(store, request.text(), message, user.getId()));
return new AIResponse(message);
}
}
큰 변경사항은 아래와 같다.
- apiUrl 선언 위치를 Service 레이어에서 FeignClient 로 변경
- ChatResponse 생성 방식 변경
restTemplate.postForObject(requestUrl, chatRequest, ChatResponse.class);
geminiFeignClient.createChat(apiKey, new ChatRequest(requestText));
ChatRequest 선언 방식은 내 가독성을 위해 변경한 코드이니 이 부분 말고,
restTemplate.postForObject() -> geminiFeignClient.createChat() 부분을 보면 된다.
이전에는 직접 Url에 key를 넣어서 전달한 반면, FeignClient를 사용하면 그냥 @Value로 받아온 key값만 전달해주면 된다.
당연하겠지만, class 선언도 필요없다.
이렇게 FeignClient로 방식 변경 끝~
다음 기록은 rate limit 적용기가 되겠다.
'대충 넘어가지 않는 습관을 위한 기록' 카테고리의 다른 글
Kafka 이벤트 수신 및 연동 기능 구현 기록 (ClassNotFoundException, SerializationException 에러 기록..) (1) | 2025.01.21 |
---|---|
Bucket4j를 이용한 Rate Limit 적용 (2) | 2024.11.13 |
Gemini api pro 연동 기록 - RestTemplate (0) | 2024.11.12 |
SSE를 이용한 실시간 알림 구현 기록 (3) | 2024.10.15 |
no-offset 적용 기록 (1) | 2024.08.19 |