30 Eylül 2021 Perşembe

Swagger @ApiResponse Anotasyonu

Giriş
Şu satırı dahil ederiz
import io.swagger.annotations.ApiResponses;
content Alanı
Örnek
Şöyle yaparız
@GetMapping
@Operation(summary = "Get the universities for a given country")
@ApiResponses(value = {
  @ApiResponse(responseCode = "200", description = "...",
               content = {@Content(mediaType = "application/json",
                                schema = @Schema(implementation = UniversityDTO.class))}),
  @ApiResponse(responseCode = "400", description = "Invalid id ",content = @Content),
  @ApiResponse(responseCode = "404", description = "...", content = @Content)})
  public List<UniversityDTO> getUniversitiesForCountry(@RequestParam String country) {
    ...
  }
}
responseCode Alanı
Örnek 
Şöyle yaparız
@Operation(summary = "Create customer")
@ApiResponses(value = {
  @ApiResponse(responseCode = "201", description = "Successfully created a customer"),
  @ApiResponse(responseCode = "400", description = "Bad Request"),
  @ApiResponse(responseCode = "401", description = "Authorization denied"),
  @ApiResponse(responseCode = "500", description = "Unexpected system exception"),
  @ApiResponse(responseCode = "502", description = "An error has occurred with an upstream service")
})
@PostMapping(consumes = JSON)
public ResponseEntity createCustomer(@Valid @RequestBody CustomerInfo customerInfo, UriComponentsBuilder uriBuilder)
  throws Exception {
  ...
}

29 Eylül 2021 Çarşamba

SpringShell @ShellMethod Anotasyonu

Giriş
shell:> diye prompt açılır. Eğer buraya help yazarsak "Application Command" ve "Built-In Commands" diye iki tane başlık görürüz. 

"Application Command" altındakiler @ShellMethod ile işaretli bizim metodlarımızdır

"Built-In Commands" altında ise 
clear
exit
help
script
stacktrace komutlarını görürüz

Metodlar içinde org.jline.jline kütüphanesine ait metodlar kullanılabilir. Çünkü SpringShell bu kütüphaneyi de beraberinde getiriyor.

Kullanım
@ShellMethod Anotasyonu, @ShellComponent olarak işaretli bir sınıf içindeki metodlara yazılır

Örnek
Şöyle yaparız.
@ShellComponent
public class SampleCommands {

  @ShellMethod("prints greeting message")
  public String greet() {
    System.out.println("Hi");
  }
}
Örnek - parametre
Şöyle yaparız.
@ShellComponent
public class Cli {
  //Call like : add — a 1 — b 2
@ShellMethod("Add two numbers together") public int add (int a ,int b){ return a + b; } }
Örnek - mandatory 
parametre
Şöyle yaparız. Komut satırından "greet Foo" yazarsak bu metod çalışır
@ShellComponent
public class SampleCommands {

  @ShellMethod("prints greeting message")
  public String greet( @ShellOption(mandatory = true) String name) {
    System.out.println("Hi" + name);
  }
}

SpringData JPA JpaRepository ile @Query ve Stream - Batch İşler İçin Uygundur

Giriş
Veri tabanından veri çekmek için genellikle şu 3 yoldan birisi kullanılıyor
1. Loading everything at once : Bellek yetmeyebilir
2. Using Paging/Slicing : Veriyi dolaşmak için kod yazmak gerekir
3. Using Streams : En kolay yöntem bu

Ne Zaman Stream Kullanılabilir
Açıklaması şöyle. Yani normalde Optional veya List döndürülür ancak Sonuç listesi çok büyükse Stream döndürülebilir
Spring Data JPA repositories in their default setup expects to return instances of either Optional or List, meaning that the result set will be lifted into memory and mapped to your model object. This works well for many use cases, but when the result set becomes very large (> 100,000 records) or the result set size isn’t known in advance, memory will become a problem. Enter Stream.

Kullanım
Stream yöntemin gerçekleşmesi için JPA 2.2'deki Query arayüzünün getResultStream() metodu kullanılıyor. Eğer sadece JPA kullanıyorsak şöyle yaparız
Stream<Author> authors = em.createQuery("SELECT a FROM Author a", Author.class)
  .getResultStream();
Spring ile şöyle yaparız
@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Integer> {

  Stream<Employee> findByHireDateBetween(LocalDate from, LocalDate to);
}

@Autowired
private EmployeeRepository employeeRepo;

@Transactional(readOnly = true)
public void exportEmployees(LocalDate hireDateFrom, LocalDate hireDateTo) {
  try (Stream<Employee> employees = employeeRepo.findByHireDateBetween(hireDateFrom,
                                                                       hireDateTo)) {
    employees.forEach(this::mapAndWrite);
  }
}

private void mapAndWrite(Employee from) {
  ...
}
Eğer N +1 Select problemi varsa şöyle yaparız
@Entity
public class Employee {
  ...

  @OneToMany(mappedBy = "employeeId", cascade = CascadeType.ALL, orphanRemoval = true)
  private Set<Salary> salaries;

  @OneToMany(mappedBy = "employeeId", cascade = CascadeType.ALL, orphanRemoval = true)
  private Set<Title> titles;
}

@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Integer> {

  @Query("SELECT DISTINCT e from Employee e "
         + "LEFT JOIN FETCH e.salaries "
         + "LEFT JOIN FETCH e.titles "
         + "WHERE e.hireDate BETWEEN ?1 AND ?2 "
         + "ORDER BY e.employeeId")
  @QueryHints(value = {
    @QueryHint(name = HINT_FETCH_SIZE, value = "" + Integer.MIN_VALUE),
    @QueryHint(name = HINT_CACHEABLE, value = "false"),
    @QueryHint(name = HINT_READONLY, value = "true"),
    @QueryHint(name = HINT_PASS_DISTINCT_THROUGH, value = "false")
  })    
  Stream<Employee> findByHireDateBetween(LocalDate from, LocalDate to);
}
- Burada join yapıldığı için Employee nesnesi veri tabanından birden fazla gelir. Bunu engellemek için DISTINCT kullanılır
- LEFT JOIN FETCH ile child nesneler eager yüklenir
- ORDER BY niçin lazım tam anlamadım

Genel kullanım için açıklama şöyle
- Any stream operation must be wrapped in a transaction. Spring will throw an exception otherwise
- A stream is created by Spring Data JPA but must be closed by you. A stream keeps a cursor to the result set open since it can’t know when the result set has been consumed. If not closed you will run out of cursors in the database. Use try-with-resources or call close on the stream.
- Forward operations only. A stream can only be consumed once
Yani 
1. Stream mutlaka kapatılmalı
2. Stream'i işleyen kod @Transactional olmalı
3. İşlenen nesne EntityManager'dan detach() çağrısı ile çıkarılmalı
4. N+1 Select problemine dikkat edilmeli


Diğer Parametreler
@QueryHint parametresi de önemli. Kullanılan bazı şeyler şöyle

- HINT_FETCH_SIZE ile bir seferde getirilecek kayıt sayısı belirtilir.

- HINT_PASS_DISTINCT_THROUGH
Açıklaması şöyle. DISTINCT kelimesi veri tabanına gönderilmez, ancak Hibernate bizim için distinct işlemini yapar.
This instruction informs Spring Data JPA/Hibernate not to pass a DISTINCT statement to the database via SQL. Instead it will interpret the DISTINCT statement in our JPQL as an instruction to Hibernate not to return the same entity one time for each row returned, i.e. it is used in conjunction with the instruction regarding DISTINCT explained above.

Örnek
Şöyle yaparız
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.jpa.repository.QueryHints;
import org.springframework.data.repository.Repository;
import javax.persistence.QueryHint;
import java.util.stream.Stream;

public interface BookRepository extends Repository<Book, Long> {
    
  @QueryHints(value = {
    @QueryHint(name = HINT_FETCH_SIZE, value = "" + Integer.MIN_VALUE),
    @QueryHint(name = HINT_CACHEABLE, value = "false"),
    @QueryHint(name = READ_ONLY, value = "true")
  })
  @Query("select b from Book")
  Stream<Book> getAll();
}
Bu repository kullanılırken, EntityManager ara sıra boşaltılmalı. Şöyle yaparız
@Component
public class BookProcessor {
  
  private final BookRepository bookRepository;
  private final EntityManager entityManager;
  
  public BookProcessor(BookRepository bookRepository, EntityManager entityManager) {
    this.bookRepository = bookRepository;
    this.entityManager = entityManager;
  }
  
  @Transactional(readOnly = true)
  public void processBooks() {
    Stream<Book> bookStream = bookRepository.getAll();
  
    bookStream.forEach(book -> {
      // do some processing
      entityManager.detach(book);
    });
    stream.close();
  }
}
Eğer .detach() metodunu çağırmazsak bir yerden sonra OutOfMemoryError hatası alırız. Açıklaması şöyle
The reason for this is that even though we are streaming entities from the database and have marked the query and transaction as read only, Hibernate keeps track of all entities in it’s persistence context. After going through a few thousand records, the heap will be full of these entities.

To resolve this we have to handle each entity in the stream in a different manner and most importantly tell Hibernate that it shouldn’t keep track of the entity in the persistence context once we are done with it.
Örnek
Elimizde şöyle bir kod olsun.
@QueryHints(value = {
  @QueryHint(name = HINT_FETCH_SIZE, value = “600”),
  @QueryHint(name = HINT_CACHEABLE, value = “false”),
  @QueryHint(name = READ_ONLY, value = “true”)
})
@Query(value = “SELECT * FROM journal_entries where accounting_entity_id = :id”, 
       nativeQuery = true)
Stream<JournalEntry> getJournalEntriesForAccountingEntity(Integer id)
Kullanmak için şöyle yaparız.
@Transactional(readOnly = true)
public String generateReportFileUrl(Integer id) throws IOException {
  Stream<JournalEntry> stream = 
    journalEntryManager.getJournalEntryStreamForAccountingEntity(id);
  ...
  stream.close();
}




28 Eylül 2021 Salı

SpringData ChainedTransactionManager Sınıfı - Deprecated

Giriş
Şu satırı dahil ederiz 
import org.springframework.data.transaction.ChainedTransactionManager;
Açıklaması şöyle
The traditional approach, using ChainedKafkaTransactionManager, has been deprecated in 2021. 
Örnek
Açıklaması şöyle
First of all, we need to enable Spring Kafka's Kafka Transaction Manager. We can do it by simply setting the transactional id property in our application.properties:

By setting this property, Spring Boot will automatically configure the Kafka Transaction Manager.
Şöyle yaparız
spring.kafka.producer.transaction-id-prefix=tx-
Sonra şöyle yaparız
@KafkaListener(id = "group1", topics = "topic1")
@Transactional("transactionManager")
public void listen1(String in) {
  log.info("Received from topic1: {}", in);

  log.info("Sending to topic2: {}", in.toUpperCase());
  kafkaTemplate.send("topic2", in.toUpperCase());

  log.info("Writing to database: {}", in);
  demoRepository.save(DemoEntity.builder()
                    .name(in)
                    .timestamp(System.currentTimeMillis())
                    .build()
  );
}
Açıklaması şöyle
The trick here is giving transactionManager as the value for the @Transactional annotation. This is because there will be two transaction managers available: transactionManager and kafkaTransactionManager.

The transactionManager bean is an instance of JpaTransactionManager while the kafkaTransactionManager bean is an instance of KafkaTransactionManager.

And the reason why we want to give the transactionManager as the value for the @Transactional annotation is because our KafkaMessageListenerContainer is already creating transactions for us on consuption. Whenever a new message comes in, it will automatically begin a Kafka transaction before it starts running our method.

Therefore, all we have to do is tell Spring Boot to, before our method is run, to also begin a transaction, but at this time, for the JpaTransactionManager.




SpringBoot DataSource Jasypt - Java Simplified Encryption

Giriş
Bir örnek  burada. application.properties dosyasında şifreli veri kullanıbilmeyi sağlar.

1. Maven
Şu satırı dahil ederiz
<dependency>
  <groupId>com.github.ulisesbocchio</groupId>
  <artifactId>jasypt-spring-boot-starter</artifactId>
  <version>3.0.4</version>
</dependency>
<plugin>
   <groupId>com.github.ulisesbocchio</groupId>
   <artifactId>jasypt-maven-plugin</artifactId>
   <version>3.0.4</version>
</plugin>
2. @EnableEncryptableProperties Anotasyonu
Şöyle yaparız
@SpringBootApplication
@EnableEncryptableProperties
public class MyApplication {
    ...
}
3. application.yml
Şöyle yaparız. Burada şifrelenmesi istenilen şeylere DEC(..) yazılıyor
jasypt:
encryptor: algorithm: PBEWithMD5AndTripleDES iv-generator-classname: org.jasypt.iv.NoIvGenerator password: ${JASYPT_ENCRYPTOR_PASSWORD} spring: datasource: url: jdbc:mysql://localhost:3306/cdr?useSSL=false username: DEC(root) password: DEC(root123)
Dosyayı şifrelemek için şöyle yaparız
mvn jasypt:encrypt -Djasypt.plugin.path=”file:src/main/resources/application.yml”
-Djasypt.encryptor.password=”secretkey”
Eğer application.properties kullanıyorsak dosya ismini vermeye gerek yok. Şöyle yaparız
mvn jasypt:encrypt -Djasypt.encryptor.password=”secretkey”
Uygulamayı JASYPT_ENCRYPTOR_PASSWORD ortam değişkenine, pluginde kullanılan secret key  değerini atayarak çalıştırmak gerekir. Sanırım dosya daha sonra şu hale geliyor. Şifrelenen şeyler ENC(...) haline geliyor
spring:
  jpa:
    database-platform: org.hibernate.dialect.H2Dialect
    hibernate:
      ddl-auto: none
  datasource:
    name: standalone-client
    url: jdbc:h2:mem:standalone-client
    username: client-user
    # password is client-password
    # provide sample-password as jasypt.encryptor.password to decrypt
    password: ENC(byF912frlZUCifdrOFlGJ8LTjWpVIutKtzU0St2X/hZ9sqRp9kOsg5Se8FVxshFu)
    driverClassName: org.h2.Driver
  h2:
    console:
      enabled: true
  application:
    name: standalone-client

management:
  endpoints:
    web:
      exposure:
        include: '*'
  endpoint:
    health:
      show-details: always

logging:
  level:
    root: info




SpringCloud Gateway RequestRateLimiter Sınıfı - API Rate Limiter İçin Kullanılır

Giriş
Şu satırı dahil ederiz
import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter;
import org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter;
Maven
Şöyle yaparız. RequestRateLimiter altta Redis kullanır. Dolayısıyla data-redis gerekir.
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifatId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
Açıklaması şöyle
RequestRateLimiter is one of the many gateway filters offered by SCG. The implementation determines whether a request is allowed to proceed or has exceeded its limit. ...
the gateway comes with one that uses a user’s Principal name. A secured gateway is needed to resolve a user’s principal name, but you have the option to implement the KeyResolver interface to instead resolve a different key from the ServerWebExchange
You can point to a custom KeyResolver bean (for example, named customKeyResolver) in the configuration by using a SPEL #{@customKeyResolver} expression. 
RequestRateLimiter sınfı RequestRateLimiterGatewayFilterFactory tarafından yaratılır

Açıklaması şöyle
The provided Redis implementation lets you define the request rate at which users can make calls within a certain time period. It also makes it possible to accommodate sporadic demands while constrained by the defined consumption rate. For example, a configuration can define a replenish rate of 500 requests per second by setting the redis-rate-limiter.replenishRate=500 property and a burst capacity of 1000 request per second by setting the redis-rate-limiter.burstCapacity=1000 property. Doing so limits consumption to 500 requests every second. If a burst in the number of requests occurs, only 1,000 requests are allowed. However, because 1,000 requests are a quota of 2 seconds, the gateway would not route requests for the next second. The configuration also lets you define how many tokens a request would cost by setting the property redis-rate-limiter.requestedTokens property. Typically, it is set to 1.

To use a gateway with a request limiting feature, it needs to be configured with the RequestRateLimiter gateway filter. 
Yani şöyle yaparız
spring:
cloud: gateway: routes: - id: route1 uri: http://localhost:8081 predicates: - Path=/backend filters: - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 500 redis-rate-limiter.burstCapacity: 1000 redis-rate-limiter.requestedTokens: 1
Açıklaması şöyle
burstCapacity, the total capacity of the token bucket.
replenishRate, the average rate at which the token bucket is filled per second.

KeyResolver  Arayüzü
Şu satırı dahil ederiz
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
Eğer KeyResolver kullanmak istersek şöyle yaparız
spring:
cloud: gateway: routes: - id: route1 uri: http://localhost:8081 predicates: - Path=/backend filters: - name: RequestRateLimiter args: rate-limiter: "#{customRateLimiter}" key-resolver: "#{customKeyResolver}" @Bean public KeyResolver customKeyResolver { return exchange -> .... // returns a Mono of String }
Çok detaylı bir örnek burada
Örnek
Şöyle yaparız
server:
  port: 8081
spring:
  cloud:
    gateway:
      routes:
      - id: limit_route
        uri: http://httpbin.org:80/get
        predicates:
        - After=2017-01-20T17:42:47.789-07:00[America/Denver]
        filters:
        - name: RequestRateLimiter
          args:
            key-resolver: '#{@hostAddrKeyResolver}'
            redis-rate-limiter.replenishRate: 1
            redis-rate-limiter.burstCapacity: 3
  application:
    name: gateway-limiter
  redis:
    host: localhost
    port: 6379
    database: 0
Açıklaması şöyle
key-resolver, the name of the bean object of the resolver for the throttled key. It uses SpEL expressions to get bean objects from the Spring container based on #{@beanName}.
Hostname özelliğine göre sınırlamak için şöyle yaparız
public class HostAddrKeyResolver implements KeyResolver {

  @Override
  public Mono<String> resolve(ServerWebExchange exchange) {
    return Mono.just(exchange.getRequest().getRemoteAddress()
      .getAddress().getHostAddress());
  }

}

@Bean
public HostAddrKeyResolver hostAddrKeyResolver() {
  return new HostAddrKeyResolver();
}
URI'ye göre sınırlamak için şöyle yaparız
public class UriKeyResolver  implements KeyResolver {

  @Override
  public Mono<String> resolve(ServerWebExchange exchange) {
    return Mono.just(exchange.getRequest().getURI().getPath());
  }
}

@Bean
public UriKeyResolver uriKeyResolver() {
  return new UriKeyResolver();
}
Örnek
Şöyle yaparız
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.web.server.ServerWebExchange;

import com.auth0.jwt.JWT;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import io.netty.util.internal.StringUtil;
import reactor.core.publisher.Mono;

public class CustomerKeyResolver implements KeyResolver {

  @Override
  public Mono<String> resolve(ServerWebExchange exchange) {
    String apiName = exchange.getRequest().getPath().toString();

    List<String> customerIds = exchange.getRequest().getHeaders().get("X-Customer-Id");
    if (customerIds != null && ...) {
      return Mono.just(customerIds.get(0) + StringUtil.COMMA + apiName);
    }

    List<String> authHeaders = exchange.getRequest().getHeaders().get("Authorization");
    if (authHeaders != null && ...) {
      ...
    }

    return Mono.just(StringUtil.EMPTY_STRING);
  }
}

27 Eylül 2021 Pazartesi

SpringCloud Feign Client @EnableFeignClients Anotasyonu

Giriş
Şu satırı dahil ederiz
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
Bu anotasyon @FeignClient anotasyonlarının taranması için gereklidir.

Örnek
Şöyle yaparız
@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication
public class EmployeeDashBoardServiceApplication {

  public static void main(String[] args) {
    SpringApplication.run(EmployeeDashBoardServiceApplication.class, args);
 

  @Bean
  public RestTemplate restTemplate(RestTemplateBuilder builder) {
    return builder.build();
  }
}
basePackages Alanı 
Örnek
Şöyle yaparız
@SpringBootApplication
@EnableFeignClients("io.xrio.movies.controller.client")
public class MoviesApplication {
  ...
}
clients Alanı
Örnek
Şöyle yaparız
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

import cz.zpapez.springfeignclients.slack.SlackFeignClient;
import cz.zpapez.springfeignclients.zephyr.ZephyrFeignClient;

@SpringBootApplication
@EnableFeignClients(clients = {
        SlackFeignClient.class,
        ZephyrFeignClient.class
})
public class SpringFeignClientsApplication {

  public static void main(String[] args) {
    SpringApplication.run(SpringFeignClientsApplication.class, args);
  }

}

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");
    }
}