Merhabalar, uzun bir aradan sonra bloğumun tozunu almaya geldim 🙂 Bu yazıda RestTemplate ve Webclient’tan bahsedeceğim.
RestTemplate, client tarafında senkronize HTTP isteklerini yürütmek için kullanılan spring-boot-starter-web paketinde yer alan sınıftır. Spring ayrıca spring-boot-starter-webflux paketinde WebClient adlı bir sınıfa sahiptir. Bunlar, ” bir API’ye nasıl client olunur ? ” ‘un yöntemlerindendir.
Yani external bir API üzerindeki HTTP methodlarını nasıl çağıracağımızı veya bunlardaki geri dönüş değerlerini nasıl alıp işleyeceğimize yardımcı olurlar. Biz bu yazı da ikisini de ayrı ayrı projelerde örnekleyeceğiz. Hangisi ile yolunuza devam edersiniz sizin kararınız . Bu karar üzerinde önemli etkiye sahip olacağını düşündüğüm bir bilgilendirme yapacak olursam şöyle ki, springin dökümantasyonunda olan bir notu paylaşmak istiyorum ,
NOTE: As of 5.0 this class is in maintenance mode, with only minor requests for changes and bugs to be accepted going forward. Please, consider using the
org.springframework.web.reactive.client.WebClient
which has a more modern API and supports sync, async, and streaming scenarios.
Rest-template (blocking client), thread-per-request modeline dayanan Java Servlet API’sini kullanır. Bu, client yanıtı alana kadar thread’in blocklanacağı anlamına gelir. Uygulamada çok sayıda istek atılıyor ise bununla orantılı olarak çok sayıda thread kullanılacak ve connection oluşacaktır. Sonuç olarak, uygulama, thread havuzunu tüketecek veya tüm kullanılabilir belleği işgal edecek birçok thread oluşturacaktır . Sık sık CPU bağlamı (thread) geçişi nedeniyle performans düşüşü yaşanacaktır..
WebClient , Spring WebFlux kütüphanesinin bir parçasıdır. WebClient, spring reactive framework tarafından asynchronous,non-blocking işlemleri destekleyen reactive bir HTTP client’dır. Reactive işlemlerin yanısıra senkron ve blocking istekleri de destekler.
RestTemplate her event (HTTP call) için thread kullanırken, WebClient her event için “task” create eder. Arka tarafta işin nasıl yürüdüğüne bakacak olursak, Reactive framework bu “tasks” sıraya (Data Stream) koyar ve bunları yalnızca uygun yanıt oluştuğunda (execute) yürütür. Yani bir işlem adımının yürütülebilmesi için başka işlemlerin sonucu beklenmek zorunda değildir. Çalışma zamanında, asenkron bir işlem yürüten kod satırı çalıştırıldıktan sonra, işlemin bitmesi beklenmeden diğer satırdaki işlemler yürütülebilir. Multithread işlemler yürütebilen sistemlerde, asenkron yürütülecek olan işlemler, farklı thread’ler tarafından da yapılabilir.
Önemli bir not ise WebClient block()
metoduyla birlikte RestTemplate benzeri senkron işlemleri de desteklemektedir.
Neden Asenkron çalışma ?
Programlamada asenkron çalışma, uygulamaları daha duyarlı hale getirmek amacıyla kullanılır. Asenkron işlemler genellikle uzun zaman alan işlemlerdir. Bu nedenle ana uygulama bir görevi yürütürken,” kilitlenme” ve “donma” adındaki yavaşlamaları kullanıcıya yansıtmadan, iyi bir kullanıcı deneyimi sunmayı sağlar. Asenkron işlemler dış kaynaktan bir veriyi almak olabileceği gibi localde uzun ve karmaşık hesaplama gerektiren işlemler de olabilir.
Sonuç olarak, Reactive yaklaşım, synchronous/blocking yöntemine kıyasla daha az thread ve sistem kaynağı kullanırken daha fazla mantık işleyebilir.
Önemli bir not daha şu ki , bir uygulamanın reactive olması için uçtan uca bütün işlemlerin reactive yapıya uygun olması gerekir, başka servislere atacağımız Rest isteklerin ve testlerde kullanılacak Http isteklerinin de reactive bir http client altyapısına sahip olması gerekir.
Haydi gelin bunların nasıl kullanıldıklarını canlı canlı görelim ..
Client’ı olacağımız API Github Rest API olacak , herkese açıktır API dökümantasyonuna adresten ulaşabilirsiniz.
RestTemplate ile başlayalım,
pom.xml dosyamıza aşağıdaki bağımlılığı eklemek yeterli olacaktır.
1 2 3 4 5 |
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.2.2.RELEASE</version> </dependency> |
https://api.github.com/users istek attığımızda karşımıza github’daki kullanıcıların json yapıda listelendiğini göreceksiniz. Şimdi bu yapıyı karşılayabileceğimiz pojo sınıfımızı oluşturalım . Ben kullanıcının response da ki tüm bilgilerini değil de bazı bilgilerini görüntülemek istiyorum model,
1 2 3 4 5 6 7 8 9 10 11 |
@Data public class GithubUser { private long id; private String name; private String login; private String avatar_url; private String repos_url; } |
Spring projemizin run edileceği sınıfta @Bean annotation’ını kullanarak RestTemplate sınıfından otomatik olarak bir instance oluşturmamızı sağlıyacak bir yöntem geliştirelim. Buradaki logic, spring boot uygulaması initialize olduktan sonra RestTemplate() methodunu çağıracak ve bu methoddan oluşan nesneyi götürüp Iaas conteiner içine koyacak buradan da biz diğer classlar içinde ne zaman @Autowire diyerek yada constructInjection kullanarak restTemplate() bunu kullanmak istersek bu instance’a bağlanıp kullanıyor olacağız.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@SpringBootApplication public class RestTemplateApplication { @Bean public RestTemplate restTemplate(){ return new RestTemplate(); } @Bean public HttpHeaders httpHeaders(){return new HttpHeaders();} public static void main(String[] args) { SpringApplication.run(RestTemplateApplication.class,args); } } |
Şimdi ise Service sınıfımızı oluşturalım ve biraz da burada konuşalım,
GITHUB_API isminde bir base uri tanımladık bunu da RestTemplate’in getForEntity methodunda kullandık. Peki bu method ne anlama geliyor,
getForEntity aldığı parametrelerle kendisine bir URI verilmesini ve bu uri’den dönen nesneyi serialize edebileceği class’ın verilmesini istiyor. Biz de aşağıda istediğini yapmış olduk,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@Service public class GithubUserService { @Autowired private RestTemplate restTemplate; private static String GITHUB_API = "https://api.github.com/users"; public GithubUser[] getUsers() { GithubUser[] result = restTemplate.getForEntity(GITHUB_API,GithubUser[].class).getBody(); return result; } |
şuan ki aşamada basitçe bir api’ye RestTemplate ile client olmuş olduk gelin şimdi de controller yazarak verileri çekmek için istekte bulunalım,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@RestController public class GithubUserController { @Autowired private GithubUserService githubApiUserService; @GetMapping("/") ResponseEntity<GithubUser[]> getAllUsers(){ GithubUser[] users = githubApiUserService.getUsers(); return new ResponseEntity<>(users,HttpStatus.OK); } } |
postman kullanarak get request gerçekleştirdiğimde RestTemplate kullanarak github api’nin bize sunduğu kullanıcıları almış olduk.
Şimdi ise aynı örneği WebClient kullanarak gerçekleştirelim.
WebClient Spring Web Flux’ın bir parçası olduğu için spring projemize “spring-boot-starter-webflux
” bağımlılığını eklemeliyiz.
1 2 3 4 |
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> |
Bu projede bir configuration sınıfı oluşturdum böylece bütün temel ayarları builder ile bir defa tanımlayabilir ve sonrasında build edilmiş instance’ı kullanabiliriz. Her seferinde yeniden ayarları düzenlememize gerek kalmadan uygulamamızı geliştirmeye devam edebiliriz.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@Component public class Configuration { private static String BASE_URL = "https://api.github.com"; @Bean public WebClient webClient() { return WebClient.builder() .baseUrl(BASE_URL) .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .build(); } } |
WebClient get()
, post()
, put()
, patch()
, delete()
, options()
veya head()
yöntemleri destekler.
Diğer işlemlere geçmeden önce birkaç yapıya göz atmamız gerekiyor.
uri()
ile birlikte path’i, path variable’ları ve request parametrelerini belirtebiliriz.headers()
ile birlikte isteği atarken header’ları gönderebiliriz.cookies()
ile birlikte Cookie’lerimizi tanımlayabiliriz.retrieve()
metodunu isteği gerçekleştirmek ve server’dan aldığımız response’u veya hataları okumak için kullanabilirz.exchange()
ile birlikte daha kontrollü bir response işleme yapısı kurabiliriz.exchangeToFlux()
veyaexchangeToMono()
metotlarını kullanarak direkt Flux ya da Mono objelerine dönüşüm sağlayabiliriz.retryWhen()
ile birlikte retry mekanizması kurabiliriz.
Son olarak attığımız işleme gelen cevabıbodyToMono()
ile Mono’ya ve bodyToFlux()
ile Flux’a dönüştürme işlemleri gerçekleştirebiliriz.
Mono: 0 ya da 1 tane event içerebilen Publisher’lar için kullanılır.
Flux: 0..N tane event’ı içeren publisher’lar için kullanılır.
WebClient ile Asenkron İstekler Göndermek
WebClient Bean’imizi oluşturduğumuza göre onu kullanarak hızlıca asenkron istek atabilen bir metot yazabiliriz.
1 2 3 4 5 6 7 8 9 10 11 |
public Mono<GithubUser[]> getUsersWithTimeOut(){ return this.webClient .get() .uri("/users").accept(MediaType.APPLICATION_JSON) .httpRequest(httpRequest -> { HttpClientRequest reactorRequest = httpRequest.getNativeRequest(); reactorRequest.responseTimeout(Duration.ofSeconds(2)); }).retrieve().bodyToMono(GithubUser[].class); } |
WebClient ile Senkron İstekler Göndermek
Bir işlemi senkron gerçekleştirebilmek için response’da dönen objeyi .toEntity()
metoduyla belirtebilir .block()
metoduyla isteğin blocking ve senkron olmasını sağlayabiliriz.
1 2 3 4 5 6 7 8 |
public Mono<ResponseEntity<GithubUser[]>> getUsers(){ return this.webClient .get() .uri("/users") .retrieve() .toEntity(GithubUser[].class).block(); } |
Örneklediğimiz bu yapı ile RestTemplate’in yerine de kullanılabilir bir yapı elde etmiş oluyoruz.
WebClient ile Error Handling
Hataları kontrol edip, kendi custom exception’larımızı da oluşturabileceğimiz yöntemlerden birisi .onStatus()
ile hata durumlarını handle etmektir. .onStatus()
metodu ile birlikte client ya da server tarafından oluşan hataları handle edebiliriz.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public Mono<GithubUser[]> getUsersWithErrorHandling(){ return this.webClient .get() .uri("/users").accept(MediaType.APPLICATION_JSON) .retrieve() .onStatus(HttpStatus::is4xxClientError, response -> { System.out.println("4xx error"); return Mono.error(new RuntimeException("4xx")); }) .onStatus(HttpStatus::is5xxServerError, response -> { System.out.println("5xx error"); return Mono.error(new RuntimeException("5xx")); }).bodyToMono(GithubUser[].class); } |
Son olarak WebClient ile birlikte senkron, asenkron operasyonların yanında error handling gibi konulara da değinmiş olduk.
Sonuç
WebClient Http isteklerinizi reactive olarak atabilmenizi sağlayan ve RestTemplate’e alternatif olarak sunulan en önemli Spring WebFlux kütüphanelerinden biridir. Hatırlatmak gerekirse uygulamanızda WebClient kullanıyor olmanız uygulamanızın reactive olduğu anlamına gelmez. Yazının başında da bahsetttiğimiz gibi bir uygulamanın reactive olabilmesi için uçtan uca asenkron ve non-blocking operasyonları içerebilmesi gerekmektedir hatta bir veritabanı kullanıyorsanız veritabanınında reactive desteği bulunması gerekmektedir.
RestTemplate projesi için spring-boot-rest-template adlı github repomu inceleyebilirsiniz
WebClient örneklediğim proje için spring-boot-webclient-rest adlı Github repomu inceleyebilirsiniz.