OpenFeign이란?
https://spring.io/projects/spring-cloud-openfeign
- 선언형 REST 클라이언트라고 한다.
- 보통 스프링에서 다른 서버와 REST 통신을 하고 싶을 땐 RestTemplate이나 WebClient 같은 걸 많이 쓴다.
- OpenFeign을 사용하면 인터페이스 형태로 RestController 작성하듯이 편하게 REST 통신을 위한 코드를 작성할 수 있다.
- 그리고 MSA 환경에서 각 FeignClient의 이름을 지정해 사용함으로써 각 서비스 간의 통신을 편하게 할 수 있다는 장점도 있다.
예제 레포 주소
https://github.com/purewater02/Feign
의존성 추가
- Spring Initializer를 사용해서 spring-cloud-starter-openFeign을 추가해 주면 된다.
- 추가할 때 dependencyManagement라는 것이 같이 추가되는데 스프링 부트 버전에 따라 springCloudVersion을 맞는 쌍으로 맞춰주기 위한 장치이다.
- ${springCloudVersion} 변수를 통해 버전을 관리하게 되는데 이 변수는 ext 블럭에서 세팅한다.
ext {
set('springCloudVersion', "2023.0.0")
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
// openfeign 추가
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
// https://mvnrepository.com/artifact/org.apache.commons/commons-lang3
implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.12.0'
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
FeignClient 활성화
- 메인 함수가 있는 클래스에 @EnableFeignClients를 추가해준다.
package com.example.feign;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableFeignClients
@SpringBootApplication
public class FeignApplication {
public static void main(String[] args) {
SpringApplication.run(FeignApplication.class, args);
}
}
- config 패키지를 만들고 Feign 클라이언트들에 대해 Global로 설정하는 값이 들어갈 FeignConfig 클래스를 그 안에 넣는다.
- Logger는 밑에서 설명한다. feign.Logger를 사용해야 한다.
package com.example.feign.config;
import com.example.feign.feign.logger.FeignCustomLogger;
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FeignConfig {
// Global FeignConfig
@Bean
public Logger feignLogger() {
return new FeignCustomLogger();
}
}
- feign 클라이언트 관련된 것들을 모아둘 패키지 feign을 만든다.
- feign 안에 client, config, decoder, interceptor, logger, common을 만든다.
DemoFeignConfig
- 각 클라이언트별로 따로 적용되어야 하는 config들을 빈으로 등록하기 위한 클래스이다.
- 어떤 클래스에는 A 인터셉터와 A 에러디코더를, 어떤 클래스에는 A 인터셉터와 B 에러디코더를 등록하거나 하기 위함이다.
package com.example.feign.feign.config;
import com.example.feign.feign.decoder.DemoFeignErrorDecoder;
import com.example.feign.feign.interceptor.DemoFeignInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DemoFeignConfig {
// Global config가 아닌 특정 클라이언트마다 다른 설정들을 모아둠.
@Bean
public DemoFeignInterceptor demoFeignInterceptor() {
return DemoFeignInterceptor.of();
}
@Bean
public DemoFeignErrorDecoder demoFeignErrorDecoder() {
return DemoFeignErrorDecoder.of();
}
}
DemoFeignInterceptor
- Feign 클라이언트의 인터셉터 구현이다.
- 로깅은 Slf4j를 사용한다.
- @RequiredArgsConstructor(staticName = "of")를 사용하면 DemoFeignInterceptor.of()로 static하게 클래스를 생성할 수 있다.
- feign.RequestInterceptor 인터페이스를 구현해준다.
- 특
- 정 HTTP 메서드 요청일 때 특정 동작을 하게 하는 등의 일반적으로 생각하는 인터셉터의 기능을 한다.
- 여기선 단순히 GET, POST 일 때 쿼리파람과 Body를 로그에 찍는 로직만 추가했다.
package com.example.feign.feign.interceptor;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpMethod;
@Slf4j
@RequiredArgsConstructor(staticName = "of")
public class DemoFeignInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
// get 요청일 경우
if (Objects.equals(requestTemplate.method(), HttpMethod.GET.name())) {
log.info("[GET] [DemoFeignInterceptor] queries : " + requestTemplate.queries());
}
// post 요청일 경우
if (requestTemplate.method().equals(HttpMethod.POST.name())) {
String encodedBody =
StringUtils.toEncodedString(requestTemplate.body(), StandardCharsets.UTF_8);
log.info("[POST] [DemoFeignInterceptor] requestBody : " + encodedBody);
// 받은 바디 변경 등 추가 로직을 정의할 수 있다.
String convertedRequestBody = encodedBody;
requestTemplate.body(convertedRequestBody);
}
}
}
DemoFeignErrorDecoder
- Feign 클라이언트가 에러를 마주했을 때 어떻게 처리할 것인지를 구현해줘야 한다.
- Feign 클라이언트 전용 익셉션 핸들러 같은 거라고 생각하면 된다.
- 여기선 HttpStatus 코드가 404인 경우에 RuntimeException을 발생시키는 로직이 추가되어 있다.
- Feign 클라이언트에서 발생한 에러를 우리가 스프링 애플리케이션 전반적으로 만들어 놓은 익셉션 핸들러가 처리할 수 있도록 조정할 수 있다.
- 실제 애플리케이션 코드에서는 여기서 RestControllerAdvice를 사용해서 만든 익셉션 핸들러가 받을 수 있는 에러로 돌려주는 기능을 추가할 수 있다.
package com.example.feign.feign.decoder;
import feign.Response;
import feign.codec.ErrorDecoder;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
@Slf4j
@RequiredArgsConstructor(staticName = "of")
public class DemoFeignErrorDecoder implements ErrorDecoder {
private final ErrorDecoder errorDecoder = new Default();
@Override
public Exception decode(String methodKey, Response response) {
HttpStatus httpStatus = HttpStatus.resolve(response.status());
// 특정 코드에 대해서는 응답을 핸들링해준다.
assert httpStatus != null;
if (httpStatus.equals(HttpStatus.NOT_FOUND)) {
log.info("[DemoFeignErrorDecoder] Http Status = " + httpStatus);
throw new RuntimeException(
String.format("[DemoFeignErrorDecoder] Http Status is %s", httpStatus));
}
// 특정 코드가 아닌 경우에는 default 설정을 따른다.
return errorDecoder.decode(methodKey, response);
}
}
FeignCustomLogger
- Feign 클라이언트에서 쓸 로거를 설정하는 곳이다.
- 아예 수동으로 구현할 수도 있다. 각자의 업무 환경이 요구하는 로그 포멧이 따로 있는 경우에는 여기서 따로 설정해줘야 한다.
- 예제에서는 간단하게 Slf4j를 사용하고 있기 때문에 친절하게도 이미 구현되어 있는 Slf4jLogger를 상속받았다.
package com.example.feign.feign.logger;
import feign.slf4j.Slf4jLogger;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public class FeignCustomLogger extends Slf4jLogger {}
DemoFeignClient
- @FeignClient를 사용해서 Http 요청을 보내기 위한 인터페이스를 작성한다.
- name 에 설정한 이름으로 application.yaml에서 각 feign 클라이언트의 속성을 변경할 수 있다.
- url은 요청을 보낼 서버의 주소이다. 마찬가지로 보통 application.yaml에 등록해서 쓴다.
- configuration에는 아까 위에서 작성한 DemoFeignConfig에서 빈으로 등록해 둔 설정들을 적용하는 곳이다. 여러개를 등록할 수도 있다.
- 특정 서버에 Get, Post 요청을 날릴 수 있는 코드이다.
- RestController의 메서드를 작성하듯이 작성하면 된다.
package com.example.feign.feign.client;
import com.example.feign.feign.common.dto.BaseRequestInfo;
import com.example.feign.feign.common.dto.BaseResponseInfo;
import com.example.feign.feign.config.DemoFeignConfig;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@FeignClient(
name = "demo-client",
url = "${feign.url.prefix}",
configuration = DemoFeignConfig.class)
public interface DemoFeignClient {
@GetMapping("/get")
ResponseEntity<BaseResponseInfo> callGet(
@RequestHeader("CustomHeaderName") String customHeader,
@RequestParam("name") String name,
@RequestParam("age") Long age);
@PostMapping("/post")
ResponseEntity<BaseResponseInfo> callPost(
@RequestHeader("CustomHeaderName") String customHeader,
@RequestBody BaseRequestInfo baseRequestInfo);
@GetMapping("/error")
ResponseEntity<BaseResponseInfo> callError();
}
request, response Dto
package com.example.feign.feign.common.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class BaseRequestInfo {
private String name;
private Long age;
}
package com.example.feign.feign.common.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class BaseResponseInfo {
private String name;
private Long age;
private String header;
}
'Backend > spring boot' 카테고리의 다른 글
redis, sentinel 설치 (0) | 2024.03.11 |
---|---|
Logback에 대해 알아보자 (0) | 2024.03.10 |
스프링에서 비동기 처리하기 @Async (0) | 2024.03.09 |
Spring batch란? (0) | 2024.03.07 |