REST Tüketimi (RestClient / RestTemplate / WebClient)
Bir uygulama sık sık başka servislerin API'lerini çağırır: bir ödeme servisi, bir döviz kuru API'si, başka bir mikroservis. Bu konuya "REST tüketimi" (consuming REST) denir. Önceki konuda (topic 01) API sunmayı öğrend…
REST Tüketimi (RestClient / RestTemplate / WebClient)
Bir uygulama sık sık başka servislerin API'lerini çağırır: bir ödeme servisi, bir döviz kuru API'si, başka bir mikroservis. Bu konuya "REST tüketimi" (consuming REST) denir. Önceki konuda (topic 01) API sunmayı öğrendik; burada başka API'leri çağırmayı ele alıyoruz. Spring'in üç istemcisi vardır; modern seçim RestClient'tır.
Üç istemci
| İstemci | Tür | Durum |
|---|---|---|
| RestClient | Senkron, akıcı (fluent) | Önerilen (Spring 6.1+) |
RestTemplate | Senkron, klasik | Bakım modunda (eski projeler) |
WebClient | Asenkron/reaktif | Reaktif (WebFlux) veya asenkron gerektiğinde |
RestClient ile çağrı
RestClient akıcı bir API sunar; bir kez oluşturulup paylaşılır:
RestClient istemci = RestClient.create("http://localhost:8080"); // GET + nesneye bağlama Doviz d = istemci.get().uri("/uzak/doviz/usd").retrieve().body(Doviz.class); // GET liste (jenerik tip için ParameterizedTypeReference) List<Doviz> hepsi = istemci.get().uri("/uzak/doviz") .retrieve().body(new ParameterizedTypeReference<>() {}); // POST + gövde var cevap = istemci.post().uri("/uzak/hesapla").body(Map.of("miktar", 100)) .retrieve().body(SomeType.class);
Örnek 1 (./Ornek1.java) bir "uzak" API sunar ve onu RestClient ile tüketir: GET (tek/liste),
POST (gövdeli) ve hata yönetimi.
Hata yönetimi
Varsayılan olarak 4xx/5xx durumlar istisna atar. Özelleştirmek için onStatus:
istemci.get().uri("/uzak/yok").retrieve() .onStatus(HttpStatusCode::isError, (req, res) -> { throw new RuntimeException("uzak servis hatası: " + res.getStatusCode()); }) .body(String.class);
RestTemplate (klasik)
Eski projelerde hâlâ yaygın:
RestTemplate rt = new RestTemplate(); Doviz d = rt.getForObject("http://.../doviz/usd", Doviz.class); ResponseEntity<Doviz> resp = rt.getForEntity(url, Doviz.class); rt.postForObject(url, istekGovdesi, CevapTipi.class);
RestTemplate bakım modundadır (yeni özellik almıyor); yeni kodda RestClient tercih et.
WebClient (reaktif/asenkron)
WebFlux veya bloklamayan G/Ç gerektiğinde:
WebClient wc = WebClient.create("http://..."); Mono<Doviz> mono = wc.get().uri("/doviz/usd").retrieve().bodyToMono(Doviz.class);
Üretim pratikleri
- Zaman aşımı ayarla (bağlantı + okuma); yavaş bir uzak servis uygulamanı kilitlemesin.
- Yeniden deneme + devre kesici (circuit breaker): Geçici hatalar için Resilience4j (retry, circuit breaker) ile çağrıyı güvenceye al.
- Bağlantı havuzu: RestClient/WebClient bir HTTP istemci kütüphanesi (Apache HttpClient, Reactor Netty) ile yapılandırılır.
- Sanal thread'ler (Java 21): Senkron
RestClientçağrıları, sanal thread'lerle bloklama maliyeti olmadan ölçeklenir (topic 101).
Özet
Başka servislerin API'lerini tüketmeyi öğrendik: modern RestClient ile GET/POST, jenerik tip
bağlama ve hata yönetimi (onStatus; Örnek 1); klasik RestTemplate ve reaktif WebClient
alternatifleri; zaman aşımı, retry/circuit breaker ve sanal thread gibi üretim pratikleri. Sırada,
dosya yükleme/indirme: File Handling.
▶ Kod Örnekleri(1)
Ornek1
ortam gerekir1// Ornek1: REST tüketimi — başka bir servisin API'sini çağırmak (RestClient).
2// Uygulama hem bir "uzak" API sunar hem de onu bir istemciyle tüketir (tek JVM'de gösterim).
3// Çalıştırma: portal gömülü Tomcat ile başlatır, self-test çıktısını alır.
4package com.egitim.springboot.resttuketim;
5
6import org.springframework.boot.CommandLineRunner;
7import org.springframework.boot.autoconfigure.SpringBootApplication;
8import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
9import org.springframework.boot.SpringApplication;
10import org.springframework.context.annotation.Bean;
11import org.springframework.http.HttpStatusCode;
12import org.springframework.web.bind.annotation.*;
13import org.springframework.web.client.RestClient;
14
15import java.util.List;
16import java.util.Map;
17
18@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
19public class Ornek1 {
20
21 public static void main(String[] args) { SpringApplication.run(Ornek1.class, args); }
22
23 record Doviz(String kod, double kur) {}
24
25 // "Uzak" servis (gerçekte başka bir mikroservis/3. parti API olurdu).
26 @RestController
27 @RequestMapping("/uzak")
28 static class UzakApi {
29 @GetMapping("/doviz/{kod}") Doviz kur(@PathVariable String kod) {
30 return new Doviz(kod.toUpperCase(), kod.equalsIgnoreCase("usd") ? 32.5 : 35.1);
31 }
32 @GetMapping("/doviz") List<Doviz> hepsi() { return List.of(new Doviz("USD", 32.5), new Doviz("EUR", 35.1)); }
33 @PostMapping("/hesapla") Map<String,Object> hesapla(@RequestBody Map<String,Object> istek) {
34 double miktar = ((Number) istek.get("miktar")).doubleValue();
35 return Map.of("sonuc", miktar * 32.5, "birim", "TRY");
36 }
37 }
38
39 @Bean
40 CommandLineRunner selfTest() {
41 return args -> {
42 // RestClient (Spring 6.1+ önerilen senkron istemci). Bir kez oluştur, paylaş.
43 RestClient istemci = RestClient.create("http://localhost:8080");
44 System.out.println("\n========= REST TÜKETİMİ SELF-TEST =========");
45
46 // GET + nesneye bağlama
47 Doviz usd = istemci.get().uri("/uzak/doviz/usd").retrieve().body(Doviz.class);
48 System.out.println("GET tek -> " + usd);
49
50 // GET liste (tip referansı)
51 List<Doviz> hepsi = istemci.get().uri("/uzak/doviz")
52 .retrieve().body(new org.springframework.core.ParameterizedTypeReference<>() {});
53 System.out.println("GET liste-> " + hepsi);
54
55 // POST + gövde
56 Map<String,Object> cevap = istemci.post().uri("/uzak/hesapla")
57 .body(Map.of("miktar", 100))
58 .retrieve().body(new org.springframework.core.ParameterizedTypeReference<>() {});
59 System.out.println("POST -> " + cevap);
60
61 // HATA yönetimi: 4xx/5xx'i yakala
62 try {
63 istemci.get().uri("/uzak/yok").retrieve()
64 .onStatus(HttpStatusCode::isError, (req, res) -> {
65 throw new RuntimeException("uzak servis hatası: " + res.getStatusCode());
66 }).body(String.class);
67 } catch (Exception e) {
68 System.out.println("HATA -> " + e.getMessage());
69 }
70 System.out.println("===========================================");
71 };
72 }
73}Bu örnek Spring / Spring Boot (Gradle) ortamı gerektirir; tek dosya olarak java Ornek1.java ile çalışmaz. Orijinal portal bunu gömülü Tomcat / Spring context ile koşuyordu. Beklenen davranış yukarıdaki anlatım ve kodda açıklanmıştır.