Cloud/MSA
[MSA] Feign Client
문승주
2024. 12. 2. 21:12
반응형
본 내용은 인프런의 이도원 님의 강의 "Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA)" 내용을 바탕으로 정리한 내용입니다.
Feign Client란
- Spring Cloud에서 제공하는 선언적 HTTP 클라이언트로 인터페이스를 통해 RESTful 웹 서비스 호출을 단순화한다.
- RESTful API 호출을 코드에서 직접 관리하는 대신, 인터페이스와 애노테이션을 사용하여 더 간결하고 유지보수하기 쉬운 코드를 작성할 수 있다.
Feign Client 실습
1. UserService
pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- FeignClent 라이브러리 추가
UserServiceApplication
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class UserServiceApplication { ...
- @EnableFeignClients 애노테이션을 통해 Feign Client 기능을 활성화
OrderServiceClient
package com.example.UserService.client;
import com.example.UserService.vo.ResponseOrder;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.List;
@FeignClient(name="OrderService")
public interface OrderServiceClient {
@GetMapping("/orderService/{userId}/orders")
List<ResponseOrder> getOrders(@PathVariable String userId);
}
- Feign 인터페이스 추가
- @FeignClient는 다른 서비스(OrderService)의 이름을 지정하고, 해당 서비스에서 제공하는 REST API를 호출
@Autowired
private OrderServiceClient orderServiceClient;
@Override
public UserDto getUserByUserId(String userId) {
UserEntity userEntity = userRepository.findByUserId(userId);
if(userEntity == null) {
throw new UsernameNotFoundException("User not found");
}
UserDto userDto = new ModelMapper().map(userEntity, UserDto.class);
// RestTemplate 사용
/*String orderUrl = String.format(env.getProperty("orderService.url"), userId);
ResponseEntity<List<ResponseOrder>> orderListResponse = restTemplate.exchange(orderUrl, HttpMethod.GET, null, new ParameterizedTypeReference<List<ResponseOrder>>() { }); List<ResponseOrder> orderList = orderListResponse.getBody();*/
// FeignClent 사용
List<ResponseOrder> orderList = orderServiceClient.getOrders(userId);
userDto.setOrders(orderList);
return userDto;
}
- OrderServiceClient 선언하여 Bean에 등록
- FeignClent를 사용하여 OrerService에서 사용자 주문 목록을 가져온다.
결과 확인
- 사용자 정보 조회하면 사용자의 주문 목록도 같이 정상적으로 조회해 온다.
Feign Client 로그
application.yml
logging:
level:
com.example.UserService.client: DEBUG
- Feign Client에 대한 디버깅 로그를 DEBUG 레벨로 출력
UserServiceApplication
@Bean
public Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
- Logger.Level.FULL 을 설정하면, 요청/응답 헤더와 본문까지 포함한 상세한 로그를 볼 수 있다.
결과 확인
---> GET http://OrderService/orderService/f94f8fa2-7663-486b-9ab9-2f35726c76ac/orders HTTP/1.1
---> END HTTP (0-byte body)
<--- HTTP/1.1 200 (33ms)
connection: keep-alive
content-type: application/json
date: Thu, 04 Jul 2024 11:48:01 GMT
keep-alive: timeout=60
transfer-encoding: chunked
[{"productId":"CATALOG-001", "qty":3, "unitPrice":5000, "totalPrice":15000, "orderId":"6aa4b6fb-dfa3-4d96-8118-2de6970bf5ce"}]
<--- END HTTP (122-byte body)
- OrderService와 연동하는 과정이 로그로 출력된다.
Feign Exception
UserServiceApplication
/**
* 사용자 ID로 사용자 정보 조회
* @param userId
* @return
*/
@Override
public UserDto getUserByUserId(String userId) {
UserEntity userEntity = userRepository.findByUserId(userId);
if(userEntity == null) {
throw new UsernameNotFoundException("User not found");
}
UserDto userDto = new ModelMapper().map(userEntity, UserDto.class);
// RestTemplate 사용
/*String orderUrl = String.format(env.getProperty("orderService.url"), userId);
ResponseEntity<List<ResponseOrder>> orderListResponse = restTemplate.exchange(orderUrl, HttpMethod.GET, null, new ParameterizedTypeReference<List<ResponseOrder>>() { }); List<ResponseOrder> orderList = orderListResponse.getBody();*/
// Feign 사용
List<ResponseOrder> orderList = null;
try {
orderList = orderServiceClient.getOrders(userId);
} catch (FeignException ex) {
log.error(ex.getMessage());
}
userDto.setOrders(orderList);
return userDto;
}
- Feign Client 호출 중 주문 정보를 가져오는 과정에서 예외가 발생하면 FeignException을 잡아서 로그를 출력한다.
- getUserByUserId에서 Feign Exception 처리
Feign Error Decoder
- Feign 클라이언트가 API로부터 에러 응답을 받을 때 이를 처리하는 방법을 정의하는 컴포넌트이다.
- 기본적으로, Feign은 HTTP 요청이 실패하면 FeignException 을 발생시키는데, Error Decoder를 사용하면 이러한 에러 응답을 더 세밀하게 처리할 수 있다.
1. 설정 정보 변경
userService.yml
orderService:
url: http://OrderService/orderService/%s/orders
exception:
orderIsEmpty: order is empty
- orderService.exception.orderIsEmpty 와 같은 메시지 프로퍼티를 설정하여, 특정 에러 발생 시 이 메시지를 출력한다.
2. UserService
FeignErrorDecoder
package com.example.UserService.error;
import feign.Response;
import feign.codec.ErrorDecoder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ResponseStatusException;
/**
* Feign 에러 처리
*/
@Component
public class FeignErrorDecoder implements ErrorDecoder {
private Environment env;
@Autowired
public FeignErrorDecoder(Environment env){
this.env = env;
}
/**
* 에러 처리
* @param methodKey
* @param response
* @return
*/
@Override
public Exception decode(String methodKey, Response response){
switch (response.status()) {
case 400:
break;
case 404:
if(methodKey.contains("getOrders")) { // 응답이 404이고 getOrders 함수일 경우
return new ResponseStatusException(HttpStatus.valueOf(response.status())
, env.getProperty("orderService.exception.orderIsEmpty"));
}
break;
default:
return new Exception(response.reason());
}
return null;
}
}
- Feign 에러 핸들링을 위한 ErrorDecoder 구현
decode
메서드에서 HTTP 응답 코드에 따라 예외를 처리한다. 예를 들어404 Not Found
상태 코드일 경우,getOrders
메서드에서 오류가 발생하면orderService.exception.orderIsEmpty
프로퍼티에 정의된 메시지를 반환한다.
UserServiceImpl
/**
* 사용자 ID로 사용자 정보 조회
* @param userId
* @return
*/
@Override
public UserDto getUserByUserId(String userId) {
UserEntity userEntity = userRepository.findByUserId(userId);
if(userEntity == null) {
throw new UsernameNotFoundException("User not found");
}
UserDto userDto = new ModelMapper().map(userEntity, UserDto.class);
// ErrorDecoder
List<ResponseOrder> orderList = orderServiceClient.getOrders(userId);
userDto.setOrders(orderList);
return userDto;
}
- FeignErrorDecoder Bean에 등록
결과 확인
package com.example.UserService.client;
import com.example.UserService.vo.ResponseOrder;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.List;
@FeignClient(name="OrderService")
public interface OrderServiceClient {
@GetMapping("/orderService/{userId}/orders_no")
List<ResponseOrder> getOrders(@PathVariable String userId);
}
- getOrders 메서드 수정하고 http://127.0.0.1:8080/userService/users/f94f8fa2-7663-486b-9ab9-2f35726c76ac 호출하여 에러 발생
404 NOT_FOUND "order is empty"]
- 404 Not Found 상태 코드는
OrderService
에서 사용자의 주문 정보를 찾을 수 없다는 의미이다. - 404에러와 userService에 설정해놓은 메시지가 출력된다.
반응형