27 Eylül 2021 Pazartesi

SpringCloud OpenFeign Kullanımı

Giriş
OpenFeign projesi Netflix tarafından geliştirildi ve daha sonra açık kaynak haline geldi.

OpenFeign Ne İşe Yarar?
Açıklaması şöyle. Yani RestTemplate kullanmaktan daha kolay.
FeignClient is a library for creating REST API clients in a declarative way. So, instead of manually coding clients for remote API and maybe using Springs RestTemplate we declare a client definition and the rest is generated during runtime for use.
Açıklaması şöyle
Feign is a declarative web service client. Instead of writing the client code to call REST Services, use feign at the client side to call webservices.

We just need to declare and annotate the interface while the implementation is auto generated at runtime.

The aim of feign is to connect the client to the REST Services with minimal overhead and code. 

Netflix has stopped supporting Feign and transferred Feign to open-source community . The new project is known as OpenFeign.
Kullanım
1.  @EnableFeignClients kullanılır
2. @FeignClient kullanılır
3. Feign.builder ile özelleştirilerek kullanılır

Feign Client kullanınca bir JDK Proxy üretilir. Konuyu açıklayan bir yazı burada

Maven
Örnek
Şöyle yaparız
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
Gradle
Şöyle yaparız
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
Unit Test
Açıklaması şöyle
Spring uses OpenFeign under the hood to build the client component that does the rest-based service call and uses Netflix Ribbon to provide client-side load balancing to the provisioned client component.
Örnek
Elimizde şöyle bir kod olsun
@FeignClient(name = "messagingRestClient", path = "/messaging")
public interface MessagingRestClient {
  @GetMapping(params = {"name"})
  Message getMessage(@RequestParam("name") final String name);

  @PostMapping(params = {"name"})
  Message setMessage(@RequestParam("name") final String name, 
                     @RequestBody final Message message);

}
Şöyle yaparız. FakeFeignConfiguration ve feign çağrılarına cevap verecek FakeMessagingRestService test içinde ayağa kaldırılır. FakeFeignConfiguration içinde localhost'a işaret eden bir Ribbon client ayarı yapılır. Böylece tüm feign istekleri localhost'a gönderilir.
@SpringBootTest(
    classes = {
      MessagingRestClientBootTests.FakeFeignConfiguration.class,
      MessagingRestClientBootTests.FakeMessagingRestService.class
    },
    webEnvironment = WebEnvironment.RANDOM_PORT)
class MessagingRestClientBootTests {

 
  @RestController
  @RequestMapping(path = "/messaging")
  static class FakeMessagingRestService {

    @GetMapping(params = {"name"},produces = "application/json")
    public String getMessage(@RequestParam("name") final String name) {

      assertThat(name).isEqualTo("Foo");

      return "{\"text\":\"Hello, Foo\"}";
    }

    @PostMapping(params = {"name"}, produces = "application/json")
    public String setMessage(@RequestParam("name") final String name,
                             @RequestBody final String message)
        throws Exception {

      assertThat(name).isEqualTo("Foo");
      JSONAssert.assertEquals(message, "{ \"text\":\"Hello\" }", false);

      return "{\"text\":\"Hi, Foo\"}";
    }
  }

  @Configuration(proxyBeanMethods = false)
  static class FakeRibbonConfiguration {

    @LocalServerPort int port;

    @Bean
    public ServerList<Server> serverList() {
      return new StaticServerList<>(new Server("localhost", port));
    }
  }

  @Configuration(proxyBeanMethods = false)
  @EnableFeignClients(clients = MessagingRestClient.class)
  @EnableAutoConfiguration
  @RibbonClient(name = "messagingRestClient",
      configuration = MessagingRestClientBootTests.FakeRibbonConfiguration.class)
  static class FakeFeignConfiguration {}
}
Aynı sınıfta testleri şöyle yaparız
@SpringBootTest(
    classes = {
      MessagingRestClientBootTests.FakeFeignConfiguration.class,
      MessagingRestClientBootTests.FakeMessagingRestService.class
    },
    webEnvironment = WebEnvironment.RANDOM_PORT)
class MessagingRestClientBootTests {

  @Autowired MessagingRestClient client;

  @Test
  public void getMessage() {
    final Message response = client.getMessage("Foo");
    assertThat(response.getText()).isEqualTo("Hello, Foo");
  }

  @Test
  public void setMessage() {
    final Message message = new Message();
    message.setText("Hello");
    final Message response = client.setMessage("Foo", message);
    assertThat(response.getText()).isEqualTo("Hi, Foo");
  }
  ...
}
Logging
Açıklaması şöyle. Burada NormalizedLogger diye başka bir kütüphane var
Standard OpenFeign logger ... logs every header in separated log entries, the body goes into another log entry.

It is very inconvenient to deal with such logs in production especially in multithreaded systems.
Örnek
Şöyle yaparız
feign:
  client:
    config:
      auth:
        logger-level: FULL
Örnek
Şöyle yaparız. Burada her bir Feign Configuration için seviye veriliyor.
@Configuration
public class MyConfiguration {

  //Turn on full logging of OpenFeign clients
  @Bean
  Logger.Level feignLoggerLevel() {
    return Logger.Level.FULL;
  }
}

//application.properties
//This setup prints all requests including headers and also responses that are being sent.
//You can verify proper values in proper authorization headers by reading these logs.

logging.level.cz.zpapez.springfeignclients.zephyr.ZephyrFeignClient: DEBUG
logging.level.cz.zpapez.springfeignclients.slack.SlackFeignClient: DEBUG
Feign Configuration
Feign.builder sınıfı kullanılarak özel Feign Configuration belirtilebilir.  Bu konfigürasyon global veya FeignClient bean'e özel olabilir. FeignClient bean'e özel olması için.  Şöyle yaparız
@FeignClient(name = "...", url = "...", 
  configuration = MyFeignClientConfiguration.class)
public interface MyFeignClient {
  ...
}
Şimdi konfigürasyon seçeneklerine bakalım. Açıklaması şöyle
Under the hood feign comes with some components which are used to make a call to remote endpoints and encode/decode request response.

Client — To make HTTP call feign requires http client. By default openfeign comes with a Default Client. We can override it with ApacheHttpClient, OkHttpClient or ApacheHC5FeignClient . These feign clients are wrapper around a delegate client. For example ApacheHttpClient wraps a httpcomponents httpclient and converts the response to feign response.

Decoder — Something needs to convert the feign Response to the actual type of the feign method’s return type. Decoders are that instruments. By default spring provides an OptionalDecoder which delegates to ResponseEntityDecoder which further delegates to SpringDecoder. We can override it by defining a bean of Decoder .

Encoder — We call feign methods by passing objects to it something needs to convert it to http request body. Encoder does that job. Again By default spring provides SpringEncoder.
Alongside the above components, there is also support for caching, and metrics provided by spring feign starter.

We can create a configuration class and override the defaults for the above components.

If we want to override the default for single components feign accepts configuration arguments which we can use to define custom override for default values.
Retry
Açıklaması şöyle
Feign has baked in support for the retry mechanism. However, by default, it uses Retry.NEVER_RETR . For example, we can create a custom retry which will retry any status code > 400. Below is the code for our CustomRetryer.
Şöyle yaparız
public class CustomRetryer extends Retryer.Default {
  public CustomRetryer(long period, long maxPeriod, int maxAttempts) {
    super(period, maxPeriod, maxAttempts);
  }
  @Override
  public void continueOrPropagate(RetryableException e) {
    log.info("Going to retry for ", e);
    super.continueOrPropagate(e);
  }
  @Override
  public Retryer clone() {
    return new CustomRetryer(5,SECONDS.toMillis(1), 5);
  }
}
Açıklaması şöyle
One important fact is that feign Retry works either on IOException or RetryableException thrown from some errorDecoder . Below is what a custom decoder looks like
Şöyle yaparız
@Bean
public ErrorDecoder errorDecoder(){
  return (methodKey, response) -> {
    byte[] body = {};
    try {
    if (response.body() != null) {
      body = Util.toByteArray(response.body().asInputStream());
    }
    } catch (IOException ignored) { // NOPMD
    }
    FeignException exception = new FeignException.BadRequest(response.reason(), 
	    response.request(), body, response.headers());
    if (response.status() >= 400) {
      return new RetryableException(
        response.status(),
        response.reason(),
        response.request().httpMethod(),
        exception,
        Date.from(Instant.now().plus(15, ChronoUnit.MILLIS)),
        response.request());
    }
    return exception;
  };
}
Support for resiliency
Açıklaması şöyle
One form of resiliency is through retries we saw in the last section. Spring has CircuitBreaker support for feign. It achieves it through a separate Feign builder FeignCircuitBreaker.Builder . The actual implementation of circuitbreaker comes from resilience4j library.
Interceptor
Açıklaması şöyle
Sometimes we want to modify the request by adding some extra information. For example, we may add a header for each request. We can achieve this by using `RequestInterceptor`. For the experiment, I added the below interceptor which populates a header userid.
Şöyle yaparız
@Bean
public RequestInterceptor userIdRequestInterceptor(){
  return (template) -> {
    template.header("userid", "somerandomtext");
  };
}
Client side loadbalancing support
Açıklaması şöyle
From spring boot 2.4.0 feign has integration with spring-cloud-loadbalancer which can fetch client url info from various service discovery providers and make that info available to feign .

Usage of feign simplifies various aspects of making HTTP request. In a typical production environment, we may need to override several components like clients, decoder, errorDecoder etc . Also within the Spring ecosystem feign is nicely integrated with resiliency, load-balancing , metrics etc which makes it an automatic choice when we are working in a microservices architecture.

Anti Pattern
Feign şöyle kullanılmamalı


Feign.builder Sınıfı
Örnek
Elimizde şöyle bir arayüz olsun
public interface CustomFeignClient {

  @RequestLine(value = "GET /sample-endpoint")
  String getResponse();
}
Şöyle yaparız
@Configuration
@AllArgsConstructor
@Import({FeignClientsConfiguration.class})
public class CustomFeignClientConfig {

  private final Encoder encoder;
  private final Decoder decoder;

  @Bean
  public CustomFeignClient customFeignClient() {
    return Feign.builder()
      .requestInterceptor(interceptor -> {
        // Set Auth related headers
        interceptor.header("key", "value");
      })
      .encoder(encoder)
      .decoder(decoder)
      .errorDecoder((methodKey, response) -> {
        val defaultErrorEncoder = new ErrorDecoder.Default();
        // Handle specific exception
        return defaultErrorEncoder.decode(methodKey, response);
      }) // Set the appropriate url
      .target(CustomFeignClient.class, "http://localhost:8080/service-b");
    }
}


Hiç yorum yok:

Yorum Gönder