Spring WebFlux ile reaktif servis nasıl yazılır: Mono, Flux ve engellemeyen akış
Beceri rehberi

Spring WebFlux ile reaktif servis nasıl yazılır: Mono, Flux ve engellemeyen akış

Hazırlayan: Mindset Institute EditörlüğüGüncellendi: 6 dk okuma

Mono ve Flux ile non-blocking servis yazmanın adımları: controller, WebClient, hata yönetimi, geri basınç ve sık yapılan blokaj hataları.

WebFlux ne zaman gerçekten gerekir?

Spring WebFlux, Spring Framework'ün reaktif ve engellemeyen (non-blocking) web katmanıdır. Spring MVC her istek için bir iş parçacığı ayırıp cevabı bekler. WebFlux ise az sayıda olay döngüsü iş parçacığıyla çalışır; bir istek veritabanı ya da başka bir servisten cevap beklerken o iş parçacığı başka işe bırakılır.

Bu yüzden WebFlux her proje için hızlı çözüm değildir. Yararlı olduğu durumlar şunlardır: • Çok sayıda eşzamanlı bağlantı tutan, ağırlıklı olarak G/Ç bekleyen servisler (ağ geçidi, toplayıcı servis, akış sunan uç noktalar) • Birden çok dış servisi paralel çağırıp sonuçları birleştiren uç noktalar • Sunucudan istemciye sürekli veri akıtan senaryolar

Kod tabanınızda engelleyen kütüphaneler ağırlıktaysa, örneğin klasik JDBC erişimi, MVC çoğu zaman daha sade ve bakımı kolay seçimdir. Reaktif zincirin tamamı engellemeyen olmadığında kazanç kaybolur. Bu modüller aynı projede birlikte de bulunabilir; örneğin MVC controller içinde reaktif WebClient kullanmak mümkündür.

Mono ve Flux: temel yapı taşları

Reaktif kodun merkezinde Project Reactor'ın temel türleri vardır. Mono, sıfır ya da bir değer üretir. Flux, sıfırdan çok sayıda değere kadar bir akış üretir. Her ikisi de bir tarif gibidir: biri abone olmadıkça hiçbir şey çalışmaz. WebFlux'ta aboneliği çerçeve, cevabı yazarken kendisi yapar. Kendi kodunuzda abone olmak (subscribe çağırmak) çoğu servis kodunda gerekmez ve yanlış kullanıldığında iş parçacığı sızıntılarına yol açar.

Temel operatörlerin işi şöyle ayrılır: • map: gelen değeri dönüştürür, işlem hızlıysa ve senkronsa kullanılır • flatMap: değerden yeni bir Mono ya da Flux üretir; başka bir servise çağrı yapılan her yerde bu gerekir • zip: birkaç bağımsız kaynağı bekleyip sonuçlarını birleştirir • switchIfEmpty: boş sonuçta alternatif akışa geçer, örneğin kayıt bulunamadığında • onErrorResume ve onErrorMap: hatayı yakalar, yedek değer döner ya da hatayı daha anlamlı bir hataya çevirir

İlk alışılması zor olan nokta, değerleri doğrudan elde edemeyeceğinizdir. Bir Mono'nun içindeki değerle işlem yapmak için operatör zinciri kurarsınız.

İlk servisi yazmak: controller ve işleyici

WebFlux birden fazla programlama modelini destekler. Anotasyonlu controller, MVC'ye çok benzer; yalnızca dönüş türleri reaktiftir. Fonksiyonel uç noktalar ise rotaları ve işleyicileri kodla tanımlar. Aynı çalışma zamanında koşarlar, seçim tercihe kalmıştır.

Anotasyonlu bir örnek şöyle görünür:

@RestController @RequestMapping("/siparisler") class SiparisController { private final SiparisService servis;

@GetMapping("/{id}") Mono<Siparis> getir(@PathVariable String id) { return servis.bul(id) .switchIfEmpty(Mono.error(new SiparisBulunamadi(id))); }

@GetMapping Flux<Siparis> hepsi() { return servis.hepsi(); } }

Burada önemli olan, metodun değer değil bir Mono ya da Flux döndürmesidir. Çerçeve bu akışa abone olur ve sonuçlar geldikçe istemciye yazar. Girdi doğrulama, hata dönüştürme ve güvenlik filtreleri MVC'deki mantıkla kurulur; farkı, filtrelerin de reaktif zincir içinde çalışmasıdır.

Başka bir servisi çağırmak: WebClient

Reaktif servislerde dış çağrılar RestTemplate ile değil WebClient ile yapılır; çünkü RestTemplate çağrı süresince iş parçacığını bloklar. WebClient ise cevabı bir Mono ya da Flux olarak verir.

Örneğin bir sipariş detayı için müşteri ve stok servislerini aynı anda çağırmak gerekiyorsa şöyle ilerlersiniz: çağrıları Mono olarak hazırlarsınız, zip ile birleştirirsiniz ve sonucu tek bir cevap nesnesine çevirirsiniz. Çağrılar paralel yürüdüğü için toplam süre, hepsinin toplamı değil en yavaş olanın süresi kadar olur.

Üretimde WebClient için şunlara dikkat edin: • Her çağrıya zaman aşımı tanımlayın; sınırsız bekleyen bir akış kaynak tüketir • Bağlantı havuzu ayarlarını eşzamanlılık beklentinize göre belirleyin • Geçici hatalarda yeniden deneme (retry) kullanın, ama yalnızca tekrarı güvenli olan isteklerde • Hata durumunu cevap koduna göre ayırıp anlamlı bir uygulama hatasına çevirin

Reaktif veri erişimi ve geri basınç

Servisin kalan kısmı da engellemeyen olmalıdır. Reaktif veritabanı sürücüleri ve Spring Data'nın reaktif depoları sorguları Mono ve Flux olarak döndürür. Engelleyen bir kütüphaneyi kullanmak zorundaysanız, o çağrıyı ayrı bir zamanlayıcıya (scheduler) taşımak gerekir; aksi halde olay döngüsünü kilitlersiniz.

Geri basınç (backpressure), yavaş tüketicinin hızlı üreticiyi yavaşlatabilmesidir. Reactive Streams belirtimine göre abone, kaç öğe işleyebileceğini yayıncıya bildirir; yayıncı bu talebi aşmaz. Örneğin bir Flux'ı yavaş bir istemciye akıtırken sınırsız tamponlama bellek şişmesine yol açar. Bunu önlemek için limitRate ile talebi sınırlayabilir, onBackpressureBuffer ile tampon boyutunu açıkça belirleyebilir ya da düşürme stratejisi seçebilirsiniz.

Dayanıklılık için şu alışkanlıkları edinin: dış çağrılara zaman aşımı koymak, hata anında yedek cevap döndürmek ve aşırı yüklenmede istekleri sınırlamak. Bunlar reaktif olmayan servislerde de geçerlidir, ama reaktif modelde zincire operatör olarak eklenir.

Test etme ve hata ayıklama

Reaktif akışlar asenkron olduğu için klasik assert yeterli olmaz. StepVerifier, bir Mono ya da Flux'ın hangi sırayla hangi değerleri ürettiğini, hata verip vermediğini ve ne zaman tamamlandığını doğrulamanızı sağlar. Uç nokta testlerinde WebTestClient, sunucuyu ayağa kaldırmadan da gerçek HTTP çağrısı benzeri istek atmanıza olanak tanır.

Hata ayıklamada başlıca zorluklar vardır. Birincisi, yığın izi (stack trace) olay döngüsü iş parçacıklarında anlamsız görünür; Reactor'ın hata ayıklama kancası ve checkpoint operatörü hatanın hangi operatörde doğduğunu göstermeye yardımcı olur. Bir diğeri, log operatörü akıştaki her sinyali (abonelik, öğe, hata, tamamlanma) yazarak zincirin gerçekte ne yaptığını gösterir.

Bir sorun giderme kontrol listesi: akışa abone olunmuş mu, bir operatör zincirin ortasında null mı döndürüyor, flatMap yerine map kullanılarak iç içe Mono mu oluşmuş, engelleyen bir çağrı olay döngüsünü tutuyor mu.

Sık yapılan hatalar

  • Zincirin içinde block çağırmak: reaktif faydanın tamamını yok eder ve olay döngüsünde çalışırsa çalışma zamanı hatası verir
  • Aynı Mono'ya birden fazla yerde abone olup sorguyu yanlışlıkla tekrar tekrar çalıştırmak
  • ThreadLocal'a dayanan kodu (örneğin kullanıcı bağlamını bu yolla taşımak) reaktif zincire taşımak; bağlam için Reactor Context kullanılmalıdır
  • Hata yönetimini unutmak: yakalanmayan hata akışı sonlandırır ve istemci anlamsız bir cevap alır
  • Her şeyi reaktife çevirmeye çalışmak: basit, düşük trafikli bir iç servis için MVC çoğu zaman daha doğrudur

Öğrenme yolu olarak önce Mono ve Flux ile operatörleri küçük bir konsol örneğinde deneyin, sonra tek bir uç noktalı WebFlux servisi yazın, ardından reaktif veri erişimi ve dayanıklılık desenlerini ekleyin. Üretim izleme (metrikler, dağıtık izleme) en son adımdır ama atlanmamalıdır.

Bu beceriyi Mindset programlarında nerede çalışırsınız?

Spring Boot İleri Seviye: Reactive ve WebFlux eğitimi, klasik Spring Boot bilen arka uç geliştiricileri için kurgulanmıştır. Modül başlıkları reaktif programlama temellerinden WebFlux ile servis geliştirmeye, reaktif veri erişimine, dayanıklılık ve geri basınç yönetimine, teste ve üretimde izlemeye uzanır.

Özellikle mikroservis mimarisinde çok sayıda dış çağrı yapan ekipler için uygundur: bir ağ geçidi ya da toplayıcı servis geliştiren ekibin ortak bir reaktif tasarım diline kavuşması, kod incelemelerinde aynı hataların tekrarını azaltır. Ekip düzeyinde eğitim düşünüyorsanız mevcut servislerinizden birini atölye konusu yapmak öğrenileni doğrudan işe bağlar.

İlgili Mindset programı

Spring Boot İleri Seviye: Reactive ve WebFlux

Sık sorulanlar

Sık sorulan sorular

Spring WebFlux, Spring MVC'nin yerini mi alıyor?

Hayır. İkisi birbirinin alternatifi olarak paralel yaşar ve aynı projede birlikte bulunabilir. MVC engelleyen kütüphanelerle çalışan, geleneksel uygulamalar için hâlâ yaygın ve uygun bir tercihtir. WebFlux, yüksek eşzamanlılık ve G/Ç ağırlıklı yük için tasarlanmıştır; tüm bağımlılık zinciri engellemeyen olduğunda anlamlı fayda sağlar.

Reaktif programlama öğrenmek için Java bilmek yeterli mi?

Dil bilgisi yeterlidir ama alışmak zaman alır. Lambda ifadeleri, işlevsel arayüzler ve Stream mantığı biliniyorsa operatörleri kavramak kolaylaşır. Asıl zorluk sözdizimi değil düşünce biçimidir: değer almak yerine akış tanımlıyorsunuz ve çalıştırma aboneliğe bağlı. Küçük örneklerle başlamak bu geçişi hızlandırır.

WebFlux kodu neden hata ayıklamada zor?

Çünkü işlem tek bir çağrı yığınında ilerlemez; operatörler farklı iş parçacıklarında çalışabilir ve yığın izi hatanın kökenini göstermez. Reactor'ın hata ayıklama kancası, checkpoint operatörü ve log operatörü hatanın hangi adımda doğduğunu bulmanıza yardım eder. StepVerifier ile akışı küçük birimler halinde test etmek de sorunu erken yakalar.

WebFlux ile geleneksel veritabanı sürücüsü kullanılabilir mi?

Teknik olarak çağrılabilir, ama engelleyen bir sürücü olay döngüsü iş parçacığını tutar ve tüm servisin yanıt vermesini yavaşlatabilir. Böyle bir çağrıyı zorunlu kullanıyorsanız ayrı bir zamanlayıcıya taşımalısınız. Genel öneri, reaktif sürücü ve depolar kullanmak ya da veritabanı ağırlıklı serviste MVC'de kalmaktır.

Mono ile Flux arasında seçimi nasıl yaparım?

Sonuç en fazla bir değerse Mono, birden çok değer olabiliyorsa Flux kullanın. Örneğin kimliğe göre tek bir kayıt getirmek Mono, bir listeyi ya da sürekli gelen olayları döndürmek Flux gerektirir. Bir Flux'ı toplayıp tek bir liste halinde Mono'ya çevirmek de mümkündür, ancak büyük akışlarda belleği tüketeceğini unutmayın.

Spring WebFlux ile Reaktif Servis Geliştirme Rehberi