Spring Webflux ile Reactive Programlamaya Giriş

Gökhan Ayrancıoğlu
Delivery Hero Tech Hub
6 min readJan 10, 2022

--

Reactive programlama ile birlikte asenkron olarak işler birbirini beklemeden non-blocking biçimde dolayısıyla az kaynak ile daha çok iş yapabilme işlevini gerçekleştirmektedir. Günümüzde reactive programlama çok değerli hale gelmeye başladı ve trend konuların başında yer alıyor. “Reactive programlama nedir?” sorusunun cevabını bilmiyor ve reactive programlamaya aşina değilseniz kesinlikle önceki yazımı okumanızı öneririm.

Günümüzde özellikle büyük kitlelere hitap eden ve yüksek trafik alan uygulamalar geliştirirdiğimizde bu uygulamalar; daha az kaynakla daha fazla isteği işleyebilmeli, yüksek düzeyde yanıt verebilmeli ve en önemlisi ölçeklenmesi kolay olmalı ve hatayla kolayca başa çıkabilmelidir. Bu sorunlarla başa çıkabilmenin en iyi yollarından biri geliştirme safhasını reactive programlama ile gerçekleştirmektir.

Reactive programlama paradigmasını gerçek hayata dökebilmek için Spring çok güzel bir bileşeni bize sunuyor, java dünyasında özellikle Spring’e aşina isek Spring Webflux ile hızlıca web uygulamalarımızı Reactive olarak geliştirebiliriz.

Nedir Spring Webflux?

Spring Webflux web uygulamaları geliştirmek için reactive, asenkron ve non-blocking programlamayı destekleyen ve Project Reactor üzerine kurulu bir frameworktür. Spring WebFlux, geleneksel SpringMVC’de yer alan her isteğe bir thread(varsayılan olarak Tomcat ile) yöntemi yerine daha ölçeklenebilir backpressure desteği olan event streamlar ile asenkron ve nonblocking (varsayılan olarak Netty ile) olarak programlamayı sağlar ve kaynakları çok daha verimli kullanır. Backpressure işlenemeyecek kadar büyük bir veri akışıyla başa çıkmanın bir yoludur.

Reactive Programlama da temel olarak, threadler önceki görevlerin tamamlanmasını beklemeden kendi işlerini tamamlayabilirler ve bloklanmadan hayatlarına devam ederler. Bu yapıyı sağlayabilmek için sürecin tamamı reactive olmalı ve herhangi bir blocking işlem içermemelidir. Uygulamamızın bir kısmı dahi bloking işlemler geçekleştiriyorsa bu o uygulamayı reactive’likten uzaklaştıracaktır. Spring WebFlux ile de, veritabanından web sunucusuna kadar uygulamadaki stack tüm katmanlarıyla reactive olması gerekir.

Spring Web Flux ile Kullanılan Yapılar

Reactive streamler publisher(producer) — subscriber(consumer) modelini baz alır.

  • Publisher — Subscriberlar’dan gelen taleplere göre publish gerçekleştirir. Bir producer birden fazla subscriber’a (aboneye) hizmet verebilir.
  • Subscriber — Producer tarafından yayınlanan eventları(olay/mesaj) yakalar. Subscriber’ın alınan eventlarla başa çıkmak için dört metodu vardır: onSubscribe, onNext, onError ve onComplete
  • Subscription — Subscriber ve publisher arasındaki ilişkiyi temsil eder. veri için talepte bulunmak adına request(long n) metoduna ve eventleri iptal etmek için cancel metoduna sahiptir.
  • Processor — Nadir kullanılan, hem subscriber hem de publisher olabilen yapıdır.

İki farklı publisher yapısı webflux için çok önemli yer tutar:

  • Mono: 0 ya da 1 tane event içerebilen Publisher’lar için kullanılır.
Mono<String> stringMono = Mono.just("Hey Gokhan");
Mono<Integer> integerMono = Mono.just(1);
Mono<Object> monoError = Mono.error(new RuntimeException());
Mono<Object> monoEmpty = Mono.empty();
  • Flux: 0..N tane event’ı içeren publisher’lar için kullanılır.
Flux<String> stringFlux = Flux.just("Gokhan", "Reader", "World");
Flux<Integer> fluxRange = Flux.range(1,5);
Flux<Long> longFlux = Flux.interval(Duration.ofSeconds(1));

Bir stream oluşturduktan sonra elemanları produce edebilmek için ona abone(subscribe) olmamız gerekir.

List<String> streamData = new ArrayList<>();
Flux<String> items = Flux.just("Gokhan", "Reader", "World");
items.log().subscribe(streamData::add);
streamData.forEach(System.out::println);

.log() metodunu kullanarak tüm akışı gözlemleyebilir ve takip edebiliriz.

.log() ile akış takibi

Reactive Programlama ile Basit Web Uygulaması

İlk olarak projenin bağımlılıklarını yönetmek gerekiyor, gradle veya maven için spring-boot-starter-webflux dependency’sinin eklenmesi gerekiyor. Artık Webflux objeleriyle reactive basit bir uygulama yazabiliriz.

implementation 'org.springframework.boot:spring-boot-starter-webflux'

WebFlux ile controller katmanını geliştirmek için iki paradigma vardır:

  • Anotasyon bazlı controller (Spring MVC gibi)
  • Functional Endpoint’ler fonksiyonel endpointler geliştirmeyi ve routing üzerinden işlemlerin devam etmesini sağlar.

Rest API — Anotasyon bazlı Controller

Spring MVC’deki aynı yapıları kullanarak controller katmanını geliştirebilirsiniz. Controller bazında dönüş tipinin reactive objeler olması dışında benzerliği oldukça yüksektir, dolayısıyla Spring MVC bilen bir kişi bu controller yapısına yukarıda anlattığım temel Webflux yapılarını bildikten sonra kolayca adapte olabilir.

//Servisimizde kısaca Mono ile bir User objesi dönelim
public Mono<User> getUserById(int id) {
return Mono.just(new User(id, "Gokhan", new Random().nextInt(100) + 1));
}

Rest katmanımızı Spring MVC’de olduğu gibi @RestController ve @RequestMapping yapılarını kullanarak hızlıca oluşturabiliriz.

@RestController
@RequestMapping("api/v1/users")
public class UserController {

private final UserService userService;

public UserController(UserService userService) {
this.userService = userService;
}

@GetMapping("/{id}")
public Mono<ResponseEntity<User>> getUserById(@PathVariable int id) {
return userService.getUserById(id)
.map(ResponseEntity::ok)
.defaultIfEmpty(ResponseEntity.notFound().build());
}
}

GET isteğini karşılayan reactive bir endpoint yazdığımızda dönüş tipinin Mono olduğunu görebilirsiniz. Yine servis katmanını incelediğimizde çağırdığımız metot Mono Publisher’ı olarak return değerlerini içinde barındırır. Yapılan iş ise alınan Mono Publisher’ını map metoduyla işlemek ve gelen değere göre responsumuzu oluşturmaktır. Yaptığımız örneğe ek olarak Flux yapılarını kullanarak birden fazla event/message dönen bir endpoint oluşturabiliriz.

//User Service'inde
@Override
public Flux<User> getUsers() {
return Flux.just(
new User(1, "Gokhan", new Random().nextInt(100) + 1),
new User(2, "Gokhan", new Random().nextInt(100) + 1)
)
;
}
//Controller katmanında
@GetMapping
public Flux<User> all() {
return userService.getUsers();
}

Örnekte gösterildiği gibi servis katmanındaki metotların dönüş tiplerinde Monoveya Fluxreactive publisher’larından biri olduğuna dikkat etmeliyiz.

Flux ile birlikte streaming oluşturabiliyoruz. Aşağıdaki örnekteki gibi çok havalı :)

@GetMapping(path = "/flux", produces = MediaType.APPLICATION_STREAM_JSON_VALUE)
public Flux<User> getFlux(){
return userService.getUsers()
.delayElements(Duration.ofSeconds(1)).log();
}

Rest API — Functional Endpoint

Spring Functional Endpoint kullanarak API katmanını functional programlamaya uygun halde geliştirebiliriz. Bu yapı ile birlikte routing ve istekleri işleyebileceğimiz bir yapı oluşturabiliriz. Bu cümleler ilk başta bir anlam ifade etmeyebilir, o nedenle örneğe doğru ilerleyelim.

Anotasyon bazlı model yerine burada HandlerFunction ve RouterFunctions olarak adlandıralan yapıları kullanıyoruz.

HandlerFunction; ServerRequest” türünde bir değişkeni alan ve ServerResponse” türünde bir Mono döndüren metottur. Anotasyon bazlı yapıya benzer bir örnek gerçekleştirmek için User Controller yapısına göre HandlerFunction oluşturalım.

@Component
public class UserHandler {

private final UserService userService;
public UserHandler(UserService userService) {
this.userService = userService;
}

public Mono<ServerResponse> getUsers(ServerRequest request) {
return ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(userService.getUsers(), User.class);
}
}

Handler fonksiyonları yarattığımızda bu metotlara gelen istekleri yönlendirebilecek Router fonksiyonları RouterFunctions.route() metodu kullanılarak yazılabilir. Birden fazla router aynı Router Function metodu içerisinde .andRoute(…) kullanılarak tanımlanabilir.

@Configuration
public class RoutingHandler {

private static final String apiPrefix = "/api/v1/users";

@Bean
public RouterFunction<ServerResponse> functionalRoutes(UserHandler userHandler) {
return RouterFunctions
.route(RequestPredicates.GET(apiPrefix), userHandler::getUsers);
}
}

Functional programlama ile Reactive programlama çok güzel bir ikilidir. Bu nedenle biz ne kadar anotasyon bazlı programlamaya aşina olsak da functional programlamaya yönelik geliştirmeler için Router Functions’ları kullanabiliriz. Böylece tüm routing yönetimini framework yerine kendimiz yapabilir hale geliriz.

Yukarıdaki örneklerle birlikte bütün metotları içeren basit reactive Rest API örneğinin tamamına Github üzerinden ulaşabilirsiniz.

Özet Geç

Spring WebFlux, concurency önemsenen ve çok sayıda isteği normalden çok daha az kaynakla yönetmeyi önemseyen, ölçeklenebilirliği gerektiren uygulamalar için uygun bir seçenektir.

Spring WebFlux’u kullanmaya karar verirken uçtan uca bir reactive uygulama geliştrebilmek için tüm akışın, tüm metot ve componentlerin reactive ve asenkron olmasına dikkat edilmeli ve hiçbir işlemin blocking olmamasını sağlamamız gerektiği unutulmamalıdır. Eğer veritabanı kullanılıyorsa veritabanı driverı da reactive olmadır.

Spring Webflux nedir, nasıl bir api katmanı oluşturulabilir, temel yapıları nelerdir gibi sorulara cevap aradık ve Spring Webflux ile Reactive programlamaya giriş yapmış olduk. Bundan sonra biraz daha “Advance”/İleri Seviye diyebileceğimiz konulara göz atıp veri işleme nasıl yapılabilir, bir database nasıl reactive olabilir gibi konulara da değinip reactive programlama konusunda yetkinliklerimizi artırıcı bu seriye devam ediyor olacağız.

— Bana ulaşmak için: Twitter, Linkedin

— Kodlar konuşsun: Github

— 1:1 görüşmeler için: Superpeer

https://gokhana.dev

Serinin diğer yazılarını okumak için buyrunuz:

--

--

Gökhan Ayrancıoğlu
Delivery Hero Tech Hub

Software Engineer @Yemeksepeti • #Java • #Spring Boot • #Kotlin • #Spark • #Microservices • https://gokhana.dev