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);  
}
404 NOT_FOUND "order is empty"]
  • 404 Not Found 상태 코드는 OrderService에서 사용자의 주문 정보를 찾을 수 없다는 의미이다.
  • 404에러와 userService에 설정해놓은 메시지가 출력된다.
반응형