27 Haziran 2023 Salı

SpringMVC Declarative REST Client - OpenFeign Yerine Kullanılır

Giriş
OpenFeign yerine kullanılır. Açıklaması şöyle
HTTP Interface Client introduced in Spring 6. The HTTP Interface Client enables to definition a declarative way for HTTP services using Java interfaces. Under the hood, Spring generates a proxy class that implements the interface and performs exchanges.
Bağımlılıklar
Açıklaması şöyle
All necessary components are in the spring-web module, that happens to be a transitive dependency for the spring-boot-starter-web or the spring-boot-starter-webflux modules. However, in practice the WebFlux dependency is always required at the moment due to the HttpServiceProxyFactory for generating the clients. 
Açıklaması şöyle
Why do you need Spring Reactive Web dependencies
When creating the project above, the dependency of Spring Reactive Web was introduced, and the WebClient type was used when creating the service object of the proxy. This is because HTTP Interface currently only has built-in implementation of WebClient, which belongs to the category of Reactive Web. Spring will launch a RestTemplate-based implementation in subsequent versions.
Anotasyonlar 
Anotasyonlar şöyle
@HttpExchange
@PutExchange
@DeleteExchange

baseUrl metodu
İsteğin gönderileceği URL adresini belirtir
Örnek
Şöyle yaparız
public interface CatFactsClient {
  @GetExchange(value = "/facts")
  List<Fact> getFacts();
}

@Configuration
public class CatFactsClientConfig {

  @Bean
  public CatFactsClient catFactsClient() {
    WebClient client = WebClient.builder()
      .baseUrl("https://cat-fact.herokuapp.com")
      .build();
    HttpServiceProxyFactory factory = HttpServiceProxyFactory
      .builder(WebClientAdapter.forClient(client))
      .build();
    return factory.createClient(CatFactsClient.class);
  }
}

@Service
public class MyService {

  @Autowired
  private CatFactsClient catFactsClient;

  public List<Fact> fetchCatFacts() {
    return catFactsClient.getFacts();
  }
}
clientConnector metodu
Örnek - WebClient Connection reset by peer error
Gönderilen istekler için keep-alive seçeneğini kapatmak gerekir. Şöyle yaparız
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.support.WebClientAdapter;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;
import reactor.netty.http.client.HttpClient;


@Configuration
public class HttpProxyConfiguration {

  @Value("${tracker.url}")
  private String trackerUrl;

  @Bean
  TrackerClient trackerClient(WebClient.Builder builder) {
    var httpClient = HttpClient.newConnection().keepAlive(false); // Here
    var reactorClientHttpConnector = new ReactorClientHttpConnector(httpClient);

    var wc = builder.baseUrl(trackerUrl)
      .clientConnector(reactorClientHttpConnector)
      .build();

    var wca = WebClientAdapter.forClient(wc);
    return HttpServiceProxyFactory.builder()
      .clientAdapter(wca)
      .build()
      .createClient(TrackerClient.class);
  }
}

defaultStatusHandler metodu
HttpStatus.NOT_FOUND, HttpStatusCode::is5xxServerError gibi durumlarda ne yapılacağını belirtir

Örnek
Elimizde şöyle bir kod olsun
@HttpExchange(
        url = "/characters",
        accept = MediaType.APPLICATION_JSON_VALUE)
public interface CharacterClient {

  @GetExchange
  List<CharacterResponse> getByName(@RequestParam String lastName);

  @GetExchange("/{id}")
  Optional<CharacterResponse> getById(@PathVariable long id);

  @PutExchange(contentType = MediaType.APPLICATION_JSON_VALUE)
  CharacterResponse addCharacter(@RequestBody AddCharacterRequest request);

  @DeleteExchange("/{id}")
  void deleteById(@PathVariable long id);
}
Bu arayüzden Spring kod üretir. Daha sonra bu kodu WebClient ile birleştirmek gerekir. Şöyle yaparız
@Configuration
public class CharacterClientConfig {

  @Bean
  public CharacterClient characterClient(CharacterClientProperties properties) {
    WebClient webClient = WebClient.builder()
      .baseUrl(properties.getUrl())
      .defaultStatusHandler(
        httpStatusCode -> HttpStatus.NOT_FOUND == httpStatusCode,
        response -> Mono.empty())
      .defaultStatusHandler(
        HttpStatusCode::is5xxServerError,
        response -> Mono
          .error(new ExternalCommunicationException(response.statusCode().value())))
      .build();

    return HttpServiceProxyFactory
      .builder(WebClientAdapter.forClient(webClient))
      .build()
      .createClient(CharacterClient.class);
  }
}

@Data
@Component
@ConfigurationProperties("character-client")
public class CharacterClientProperties {
    private String url;
}
Açıklaması şöyle. Yani şimdilik HttpServiceProxyFactory + WebClientAdapter + WebClient kodunu elle yazmak gerekiyor.
Currently, unlike OpenFeign, the client is not yet supplied via auto-configuration in a Spring Boot setup (kindly track Support declarative HTTP clients #31337 for that matter). Therefore, we build a WebClient ourselves and create a declarative HTTP client from it by using the createClient method from HttpServiceProxyFactory. This is some kind of boilerplate code, but I am quite confident that the Spring Boot guys will come up with a nice solution to simplify this further. Up until this point, you will need such a bean definition for each declarative HTTP client in your application.
Kullanmak için şöyle yaparız
CharacterResponse brandon = characterClient
    .addCharacter(new AddCharacterRequest("Brandon", "Stark"));

List<CharacterResponse> starks = characterClient.getByName("Stark");

Optional<CharacterResponse> eddardStark = characterClient.getById(1);

Optional<CharacterResponse> unknown = characterClient.getById(1337L); // empty

characterClient.deleteById(brandon.id());
Eğer Sadece Web Client Kullansaydık şöyle yaparız. Bu da karışık kodlar demek
public List<CharacterResponse> getByName(String lastName) {
  return webClient
    .get()
    .uri(uriBuilder -> uriBuilder
                    .path("/characters")
                    .queryParam("lastName", "{lastName}")
                    .build(lastName))
    .retrieve()
    .toEntityList(CharacterResponse.class)
    .block()
    .getBody();
}

Hiç yorum yok:

Yorum Gönder