Eğitim Portalı/Spring Boot/REST Tüketimi (RestClient / RestTemplate / WebClient)
Spring Boot03-spring-boot/16-rest-tuketimi

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

İstemciTürDurum
RestClientSenkron, akıcı (fluent)Önerilen (Spring 6.1+)
RestTemplateSenkron, klasikBakım modunda (eski projeler)
WebClientAsenkron/reaktifReaktif (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 gerekir
Ornek1.java
1// 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}
canlı çalıştırma için ortam gerekir

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.