OpenFeign(MSA CURL)

OpenFeign 이란?


OpenFeign은 Netflix에서 개발한 오픈소스이며, Spring Cloud 프로젝트의 일부로서 선언적인 REST 클라이언트로서 서비스 간의 HTTP 통신을 자동화하는 데 사용됩니다. 
OpenFeign은 애플리케이션 간의 서비스 호출을 단순화하고 클라이언트 코드를 간결하게 유지할 수 있도록 지원합니다.

OpenFeign 사용법

1. gradle 추가

// https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign
implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-openfeign', version: '3.1.7'

 

2. @EnableFeignClients 명시

@SpringBootApplication
@EnableFeignClients
public class SnsApiApplication {
	public static void main(String[] args) {
		// 타임존 셋팅
		TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul"));
		Locale.setDefault(Locale.KOREA);

		SpringApplication.run(SnsApiApplication.class, args);
	}
}

 

 

3. 인터페이스 생성

@FeignClient(name = "users", url = "https://api.example.com")
public interface UserServiceClient {
    
	@GetMapping("/users/{id}")
    	User getUserById(@PathVariable("id") Long id);
}

 

 

4. 사용하는 곳에서 주입받아 사용

@RequiredArgsConstructor
@Service
@Transactional
public class MemberInfoService extends BaseService {

	private final UserServiceClient userServiceClient;
}

 

5. 통신 실패 및 4xx 예외 FeignException 발생

try {
    User user = feignClient.getUserById(id);
} catch (FeignException e) {
    log.error(e.getMessage);
		user = null;
}

- 실패에 대한 예외는 FeignException 발생

- try-catch로 예외처리 or ErrorDecoder 사용

- ErrorDecoder 란?

  • Feign 클라이언트에서 발생한 오류를 처리하기 위한 사용자 정의 전략을 제공
  • 특히, 서버에서 반환된 HTTP 상태 코드에 따라 다른 동작을 구현할 때 유용
  • 예제 코드
public class CustomErrorDecoder implements ErrorDecoder {

    @Override
    public Exception decode(String methodKey, Response response) {
        if (response.status() == 404) {
            return new NotFoundException("Not Found");
        }

        return new RuntimeException("Generic error");
    }
}

OpenFeign 장점

1. 통신 실패에 대한 재시도(Retry) 설정이 가능

import feign.Retryer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FeignClientConfiguration {

    @Bean
    public Retryer feignRetryer() {
        // 최대 3번 재시도
        return new Retryer.Default(100, 1000, 3);
    }
}

- ex) 요청이 실패할 경우 최대 3번 재시도하도록 설정

- Retryer의 구현체로 Retryer.Default

- Retryer.Default 생성자의 매개변수 순서는 다음과 같음

  • period: 재시도 간격(밀리초 단위)
  • maxPeriod: 최대 재시도 간격(밀리초 단위)
  • maxAttempts: 최대 재시도 횟수

- 사용하는 인터페이스에서는 configuration 속성에 해당 설정 클래스 명시 후 사용\

- 더 복잡한 설정은 Retryer 인터페이스 직접 구현하여 사용

- https://mangkyu.tistory.com/279 - (OpenFeign 타임아웃, 재시도, 로깅 설정)

 

 

2. 인터페이스 기반의 API 정의로 간단하고 직관적인 코드 작성 및 코드가 줄어듦

@FeignClient(name = "users", url = "https://api.example.com")
public interface UserServiceClient {
    
	@GetMapping("/users/{idx}")
  	User getUserById(@PathVariable("idx") Long idx);

	@PostMapping("/users")
	User registerUser(User user);

	@DeleteMapping("/users/{idx}")
	int deleteUser(@PathVariable("idx") Long idx);
}

 

- 위의 예제와 같이 선언부만 명시하면 사용이 가능

- user와 관련된 http 통신이 하나의 인터페이스에서 심플하게 관리될 수 있는 장점 지님

 

3. 익숙한 Spring MVC 어노테이션으로 개발이 가능

- 2번 예제 코드와 같이 스프링 컨트롤러에서 사용하는 어노테이션을 그대로 활용할 수 있음

- 러닝 커브가 높지 않음

 

4. Netflix에서 개발, 지속적인 라이브러리 업데이트 및 개선

- github에서 확인 할 수 있듯이 지속적으로 업데이트 중

 


@FeignClient 속성값

1. name 

@FeignClient(name = "example-service")
  • Feign 클라이언트의 이름을 지정합니다. 이 이름은 실제로 Feign 클라이언트의 빈 이름으로 사용되며, 이를 통해 해당 클라이언트를 식별할 수 있음

2. url

@FeignClient(url = "http://example.com")
  • Feign 클라이언트가 연결할 대상 서비스의 기본 URL을 지정합니다. name 대신에 **url**을 사용하여 직접 URL을 지정할 수 있음
  • @FeignClient(url = "<http://example.com>")

3.  value

@FeignClient(value = "example-service")
  • name과 동일한 역할을 함. Feign 클라이언트의 이름을 지정

4. configuration

@FeignClient(name = "example-service", configuration = MyFeignClientConfiguration.class)
  • Feign 클라이언트의 구성을 지정하는 데 사용. 구성은 각종 Feign 관련 설정을 변경할 수 있는 빈을 참조함

5.  fallback

@FeignClient(name = "example-service", fallback = MyFallback.class)
  • Feign 클라이언트에서 예외가 발생할 때 사용할 Fallback 클래스를 지정함. Fallback 클래스는 특정 메서드 호출에 대한 대체 로직을 제공함

6. fallbackFactory

@FeignClient(name = "example-service", fallbackFactory = MyFallbackFactory.class)
  • Fallback 클래스 대신 Fallback 팩토리를 지정함. Fallback 팩토리는 Fallback 인스턴스를 생성하는 데 사용됨

기존 WebClient 와 OpenFeign 차이

1. 같은 기능을 하는 HTTP 통신 코드 비교

  • WebClient 사용 시
@SneakyThrows
private List<PetDto> getPetUuidListByCurl(PetDto petDto) {
    List<PetDto> petList = new ArrayList<>(); // 리턴할 결과 값

    if (Boolean.TRUE.equals(usePetWalkCurl)) {
        // curl 통신
        WebClient webClient = WebClient.builder()
                .baseUrl(walkDomain)
                .build();

        String jsonString = webClient.get()
                .uri(uriBuilder -> uriBuilder
                        .path("/v1/pet/walk/entry")
                        .queryParam("walkUuid", petDto.getWalkUuid())
                        .queryParam("memberUuid", petDto.getMemberUuid())
                        .build())
                .retrieve()
                .bodyToMono(String.class)
                .onErrorResume(throwable -> {
                    throw new CustomException(CustomError.WALK_UUID_ERROR); // 존재하지 않는 산책 고유아이디입니다.
                })
                .block();

        // curl 통신 정상
        if (!ObjectUtils.isEmpty(jsonString)) {
            JsonParser parser = new JsonParser();
            ObjectMapper mapper = new ObjectMapper();

            JsonObject jsonObject = (JsonObject) parser.parse(jsonString); // json object 파싱

            String dataJson = jsonObject.get("data").toString();
            JsonObject listJson = (JsonObject) parser.parse(dataJson); // data 안 list 파싱
            List<String> petUuidList = mapper.readValue(listJson.get("list").toString(), List.class);

            if (!ObjectUtils.isEmpty(petUuidList)) {
                for (String uuid : petUuidList) {
                    PetDto tmpPetDto = PetDto.builder()
                            .uuid(uuid)
                            .memberIdx(petDto.getMemberIdx()).build();
                    PetDto walkPetInfo = memberPetService.getWalkPetInfo(tmpPetDto);
                    if (petDto != null) {
                        // 상태값 text setting
                        memberPetService.stateText(walkPetInfo);
                        petList.add(walkPetInfo);
                    }
                }
            }
        }
    }

    return petList;
}
  • FeignClient 사용 시
private List<PetDto> getPetUuidListByCurl(PetDto petDto) {
    if (Boolean.TRUE.equals(usePetWalkCurl)) {
			List<PetDto> petList = new ArrayList<>(); // 리턴할 결과 값
			// walk curl 통신
			List<String> petUuidList;
			
			try {
			    petUuidList = memberCurl.petUuidList(petDto.getWalkUuid(), petDto.getMemberUuid());
			} catch (FeignException e) {
			    petUuidList = null;
			}
			
			if (!ObjectUtils.isEmpty(petUuidList)) {
			    for (String uuid : petUuidList) {
			        PetDto tmpPetDto = PetDto.builder()
			                .uuid(uuid)
			                .memberIdx(petDto.getMemberIdx()).build();
			        PetDto walkPetInfo = memberPetService.getWalkPetInfo(tmpPetDto);
			        if (petDto != null) {
			            // 상태값 text setting
			            memberPetService.stateText(walkPetInfo);
			            petList.add(walkPetInfo);
			        }
			    }
			}
	}
  return petList;
}

 

→ 같은 동작을 하는 코드이지만 feignClient 사용 시 코드가 훨씬 간결해지는 것을 볼 수 있음

 

2. WebClient 와 OepnFeign 장단점

  • 공통점
    • 마이크로서비스 간 통신을 지원

WebClient

 

🟩 장점

  1. 비동기 지원: WebClient는 비동기 및 리액티브 프로그래밍을 지원. 이는 많은 동시 요청을 효과적으로 처리할 수 있게 해 주며, 리액티브 스타일의 프로그래밍이 필요한 경우에 적합
  2. 함수형 스타일: WebClient는 함수형 프로그래밍 스타일을 지원하므로, 리액티브 스트림과 같은 기능을 활용할 수 있음
  3. 더 많은 유연성: WebClient는 더 많은 커스터마이징이 가능하며, 여러 가지 HTTP 클라이언트 구현체를 지원

🟥 단점

  1. 설정 복잡성: WebClient를 설정하는 것은 상대적으로 복잡할 수 있음. 리액티브 프로그래밍과 관련된 개념을 이해해야 하며, 적절한 스케줄러 및 리액터 라이브러리를 선택해야 함
  2. Annotation이 없음: WebClient는 Feign과는 달리 어노테이션을 사용하지 않으므로, 메서드 호출에 대한 명시적인 URL 및 요청 구성이 필요함

FeignClient

 

🟩 장점

  1. Annotation 기반의 간편한 사용: FeignClient는 인터페이스와 어노테이션을 이용하여 클라이언트를 정의하므로, 선언적이고 간단한 사용이 가능
  2. 자동화된 서비스 디스커버리: Spring Cloud의 @FeignClient를 사용하면 Eureka와 같은 서비스 디스커버리와 통합되어 자동으로 서비스를 찾을 수 있음
  3. 설정이 간단: FeignClient는 어노테이션을 통해 간단하게 설정할 수 있음

🟥 단점

  1. 비동기 지원 부족: 기본적으로 FeignClient는 비동기 및 리액티브 프로그래밍을 지원하지 않음.
  2. 자유도 제한: FeignClient는 주로 마이크로서비스 간의 통신을 위한 목적으로 디자인되었으며, 일반적인 HTTP 클라이언트로 사용하기에는 몇몇 제약이 있을 수 있음

일반적으로 간단하게 서비스 간 통신을 구현하려면 FeignClient가 더 편리할 수 있지만, 더 복잡하거나 비동기적인 요구 사항이 있는 경우 WebClient를 사용하는 것이 더 적합할 수 있음

 


참고 사이트

'HTTP' 카테고리의 다른 글

HTTP란?  (0) 2024.03.29
RESTful API란?  (0) 2024.03.23
(HTTP)인터넷 네트워크  (0) 2022.05.23