[Spring Cloud] Microservices간의 통신 - RestTemplate & FeignClient
Microservices간의 통신하기
클라이언트가 user-service에서 userId를 사용하여 사용자에 대한 정보를 요청했을 시 사용자의 주문이력까지 가져와야하는 상황을 가정해보면, 요청했던 userId 값을 order-service의 파라미터로 전달해서 엔드포인트를 호출해야 주문이력을 가져올 수 있다.
RestTemplate과 FeignClient를 이용해 해당하는 정보를 가져와보자.
RestTemplate
RestTemplate은 http 통신을 유용하게 할 수 있도록 스프링에서 제공하는 템플릿이다.
Spring 5.0 이후부터 레거시 라이브러리로 간주되었다.
1️⃣ user-service에 RestTemplate을 Spring Bean으로 등록
@SpringBootApplication
@EnableDiscoveryClient
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
@Bean
@LoadBalanced // 주소가 아닌 MicroService name 으로 호출
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
2️⃣ user-service.yml 설정 변경
order_service:
url: http://ORDER-SERVICE/order-service/%s/orders
exception:
orders_is_empty: User's order is empty
3️⃣ UserServiceImpl
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService{
private final UserRepository userRepository;
private final RestTemplate restTemplate;
private final Environment env;
@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);
/* Using RestTemplate */
String orderUrl = String.format(env.getProperty("order_service.url"), userId);
ResponseEntity<List<ResponseOrder>> orderListResponse =
restTemplate.exchange(orderUrl, HttpMethod.GET, null,
new ParameterizedTypeReference<List<ResponseOrder>>() {
});
List<ResponseOrder> orderList = orderListResponse.getBody();
userDto.setOrders(orderList);
return userDto;
}
}
FeignClient
FeignClient는 Netflix에서 개발한 Http Client로, 인터페이스를 작성하고 어노테이션을 통해 Http Client를 구현할 수 있다.
1️⃣ dependencies 추가
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2️⃣ application 파일에 @EnableFeignClients
추가
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
@Bean
@LoadBalanced // 주소가 아닌 MicroService name 으로 호출
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
SpringBootApplication이 실행될 때 @FeignClient
어노테이션이 붙은 파일들을 읽고 구현체를 만들어야므로 어노테이션을 읽을 수 있게 @EnableFeignClients
어노테이션을 UserServiceApplication 파일에 붙여준다.
3️⃣ @FeignClient
사용해서 OrderServiceClient 구현
@FeignClient(name = "order-service")
public interface OrderServiceClient {
@GetMapping("/order-service/{userId}/orders")
List<ResponseOrder> getOrders(@PathVariable String userId);
}
- @FeignClient에 호출하고자 하는 microservice 이름을 지정한다. ➔ Eureka server에 인스턴스로 등록 시 사용된 이름
4️⃣ OrderServiceClient 주입받고 UserServiceImpl 구현
@Sl4fj
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService{
private final OrderServiceClient orderServiceClient; // FeignClient 인터페이스 주입
@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);
/* Using FeignClient */
/* Feign Exception Handling */
List<ResponseOrder> orderList = null;
try {
orderList = orderServiceClient.getOrders(userId);
} catch (FeignException ex) {
log.error(ex.getMessage());
}
userDto.setOrders(orderList);
return userDto;
}
}
예외처리 - ErrorDecoder
@Component
@RequiredArgsConstructor
public class FeignErrorDecoder implements ErrorDecoder {
private final Environment env;
@Override
public Exception decode(String methodKey, Response response) {
switch (response.status()) {
case 400:
break;
case 404:
if (methodKey.contains("getOrders")) {
return new ResponseStatusException(HttpStatus.valueOf(response.status()),
env.getProperty("order_service.exception.orders_is_empty"));
}
break;
default:
return new Exception(response.reason());
}
return null;
}
}
첫번째 매개변수인 methodKey에 FeignClient를 통해 호출한 함수의 이름을 포함하면 해당하는 함수가 호출됐을 때 알맞은 Exception 객체를 만들어서 반환한다.
@Slf4j
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService{
private final UserRepository userRepository;
private final 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);
/* ErrorDecoder */
List<ResponseOrder> orderList = orderServiceClient.getOrders(userId);
userDto.setOrders(orderList);
return userDto;
}
}
ErrorDecoder를 구현하면 try-catch문을 사용하지 않고 간단하게 코드를 구현할 수 있다.
참고
https://techblog.woowahan.com/2630/
https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/