Spring WebFlux හැඳින්වීම | Reactive Programming | Sinhala SC Guide

Spring WebFlux හැඳින්වීම | Reactive Programming | Sinhala SC Guide

කට්ටියට කොහොමද? තාක්ෂණික ලෝකේ දවසින් දවස අලුත් වෙන විදිය දැක්කම පුදුම හිතෙනවා නේද? අද මම අරන් ආවේ ඔයාලට Software Engineering වල තියෙන ලොකුම අභියෝගයක් විසඳන්න උදව් වෙන, ඒ වගේම ඔයාලගේ applications පට්ට වේගෙන් වැඩ කරන්න හදන්න පුලුවන්, සුපිරි concept එකක් ගැන – ඒ තමයි Spring WebFlux.

ගොඩක් දෙනෙක් තාම දන්නේ නෑ මේකෙන් පුදුම වැඩ ටිකක් කරන්න පුලුවන් කියලා. සාමාන්‍යයෙන් අපේ Java applications හදනකොට, විශේෂයෙන්ම web applications හදනකොට, requests ගොඩක් එකපාර එනකොට performance අවුල් යනවා නේද? එක request එකක් ඉවර වෙනකම් තව requests වලට බලන් ඉන්න වෙනවා. හරියට බැංකුවක queue එකක් වගේ. කෙනෙක් වැඩේ ඉවර වෙනකම් අනිත් කෙනාට මුකුත් කරන්න බෑ.

මේ වගේ අවුල් වලට විසඳුමක් විදියට තමයි Reactive Programming කියන concept එක ආවේ. Spring WebFlux කියන්නේ Spring Framework එකේ තියෙන, මේ Reactive Programming විදියට applications හදන්න පුලුවන් කරන සුපිරි Module එකක්. අද අපි කතා කරමු මේක මොකක්ද, ඇයි මේක අපිට ඕන, සහ කොහොමද මේක පාවිච්චි කරන්නේ කියලා. පොඩ්ඩක් ඉවසල මේක කියෙව්වොත්, ඔයාලට ඔයාලගේ applications වල performance අමුතුම විදියට වැඩි කරගන්න පුලුවන් වෙනවා!

Blocking vs. Non-Blocking – මොකක්ද මේ අවුල?

මුලින්ම අපි බලමු මොකක්ද මේ Blocking කියන්නේ, සහ ඇයි ඒක අවුලක් වෙන්නේ කියලා. අපි සාමාන්‍යයෙන් හදන Spring Boot applications (Spring MVC වගේ) Thread-per-request model එක මත තමයි වැඩ කරන්නේ. මේකේදී client request එකක් ආවම, server එක අලුත් Thread එකක් ඒකට assign කරනවා. මේ Thread එක client request එකෙන් එන හැම operation එකක්ම (Database call, API call, File I/O වගේ) ඉවර වෙනකම් block වෙනවා, ඒ කියන්නේ අක්‍රිය වෙනවා.


// Simple Blocking Example
public User getUserById(Long id) {
    // This line might take time if database is slow
    User user = userRepository.findById(id).orElse(null);
    return user;
}

හිතන්නකෝ ඔයාගේ server එකට එකපාර requests 1000ක් එනවා කියලා. එතකොට server එක Threads 1000ක් හදන්න උත්සාහ කරනවා. හැබැයි Threads වලට Memory වගේම CPU resourcesත් ඕන වෙනවා. එතකොට වෙන්නේ server එකේ resources ඉක්මනට ඉවර වෙලා, අලුත් requests වලට Threads හදාගන්න බැරුව, applications slow වෙලා, අන්තිමට crash වෙන එක. මේක හරියට බැංකුවක තියෙන එකම කවුන්ටරය වගේ. එක පාරට ගනුදෙනුකරුවෝ ගොඩක් ආවම, කවුන්ටරේ ඉන්න කෙනා එක කෙනෙක්ගේ වැඩේ ඉවර වෙනකම් අනිත් අයට බලන් ඉන්න වෙනවා. එතකොට පෝලිම දික් වෙනවා, මිනිස්සුන්ට එපා වෙනවා.

හැබැයි Non-Blocking I/O වලදී වෙන්නේ වෙනස් දෙයක්. Client request එකක් ආවම, server එක Thread එකක් ඒකට assign කරනවා. හැබැයි මේ Thread එක blocking operation එකක් එනකම් (Database call එකක් වගේ) block වෙන්නේ නෑ. ඒක client request එක processing කරනවා, ඊටපස්සේ blocking operation එකක් ආවම, ඒක වෙනත් Thread pool එකකට භාර දීලා, තමන් නිදහස් වෙනවා. එතකොට ඒ Thread එකට පුලුවන් තව client request එකක් process කරන්න. Blocking operation එක ඉවර උනාම, ඒක callback එකක් විදියට original Thread එකට (හෝ වෙනත් Thread එකකට) notification එකක් දෙනවා. මේක හරියට බැංකුවේ token system එක වගේ. ඔයාට token එකක් ලැබුනම, ඔයාට පුලුවන් ඉඳගෙන ඉන්න, එහෙමත් නැත්නම් වෙන වැඩක් කරන්න. ඔයාගේ වාරේ ආවම එයාලා කතා කරනවා. ඔයාට කවුන්ටරේ ඉස්සරහා බලන් ඉන්න ඕන නෑ.

මේ Non-Blocking model එකෙන් අපිට පුලුවන් පොඩි Thread ගානකින් ලොකු client requests ගානක් manage කරන්න. මොකද Threads block වෙන්නේ නැති නිසා. ඒකෙන් server resources ඉතුරු වෙනවා, application එකේ scalability එක වැඩි වෙනවා.

Reactive Programming – විසඳුම මොකක්ද?

හරි, දැන් අපි දන්නවා Non-Blocking කියන්නේ මොකක්ද කියලා. Reactive Programming කියන්නේ මේ Non-Blocking applications හදන්න පුලුවන් කරන paradigm එකක්. මේකෙන් අපිට පුලුවන් Asynchronous, Non-Blocking, Event-driven applications හදන්න. මේකේ ප්‍රධානම අදහස තමයි data stream විදියට හසුරුවන එක. මේක හරියට YouTube එකෙන් video එකක් stream වෙනවා වගේ. ඔයාට මුළු video එකම එකපාර load වෙනකම් බලන් ඉන්න ඕන නෑ, පොඩ්ඩ පොඩ්ඩ එන ගමන් බලන්න පුලුවන්.

Reactive Streams specification එකෙන් මේ data streams හසුරුවන්න ප්‍රධාන Components හතරක් හඳුන්වා දෙනවා:

  1. Publisher: මේක තමයි data produce කරන්නේ. ඒ කියන්නේ data streams වල source එක.
  2. Subscriber: මේක තමයි Publisher එකෙන් එන data consume කරන්නේ. ඒ කියන්නේ data streams වල destination එක.
  3. Subscription: Publisher එකයි Subscriber එකයි අතර තියෙන සම්බන්ධය. මේකෙන් තමයි Subscriber එකට කොච්චර data ඕනද කියලා Publisher එකට කියන්න පුලුවන් (මේකට කියන්නේ Backpressure කියලා).
  4. Processor: මේක Publisher කෙනෙකුත් වෙනවා, Subscriber කෙනෙකුත් වෙනවා. ඒ කියන්නේ එන data එකක් transform කරලා, වෙනත් Subscriber කෙනෙකුට දෙන්න පුලුවන්.

Spring WebFlux වලදී අපි මේ Reactive Streams implementation එකට Reactor කියන library එක පාවිච්චි කරනවා. Reactor වල ප්‍රධාන data types දෙකක් තියෙනවා:

Flux: මේකෙන් represent කරන්නේ 0ක්, 1ක් හරි ඊට වැඩි ගානක් හරි data items stream එකක්. ඒ කියන්නේ "this will return multiple items over time, or maybe none".


            Flux<String> names = Flux.just("Colombo", "Kandy", "Galle"); // Returns a stream of Strings
            Flux<User> allUsers = userRepository.findAll(); // Returns a stream of Users
        

Mono: මේකෙන් represent කරන්නේ 0ක් හරි 1ක් හරි data item එකක්. ඒ කියන්නේ "maybe this will return one item, or maybe nothing at all".


            Mono<String> name = Mono.just("Sri Lanka"); // Returns a single String
            Mono<User> user = userRepository.findById(id); // Returns a single User or nothing
        

Mono සහ Flux දෙකම Publisher implementations. මේවා පාවිච්චි කරලා තමයි අපි Reactive applications හදන්නේ. මේකේ තියෙන ලොකුම වාසිය තමයි, මේවා lazy විදියට වැඩ කරන එක. ඒ කියන්නේ Subscriber කෙනෙක් subscribe කරනකම් මේවා data produce කරන්නේ නෑ. මේකෙන් resources ඉතුරු වෙනවා.

Spring WebFlux – වැඩේට බහිමු!

Spring WebFlux කියන්නේ Spring Framework 5 වලින් introduce කරපු, Reactive Programming support කරන web framework එකක්. මේක Netty, Undertow වගේ Non-Blocking servers මත තමයි වැඩ කරන්නේ. මේකෙන් අපිට පුලුවන් traditional Spring MVC වගේ Controller-based web applications හදන්න, එහෙමත් නැත්නම් Functional Endpoints කියන අලුත් ක්‍රමය පාවිච්චි කරන්න. ඒ ගැන අපි පස්සේ කතා කරමු.

WebFlux වල ප්‍රධාන වාසි කිහිපයක් මෙන්න:

  • Scalability: අඩු resources ප්‍රමාණයක් පාවිච්චි කරලා, වැඩි requests ගානක් handle කරන්න පුලුවන්.
  • Resilience: System එකේ කොටසක් slow උනත්, අනිත් කොටස් වලට ඒක බලපාන්නේ නැති වෙන්න හදන්න පුලුවන්.
  • Performance: Non-Blocking I/O නිසා data processing වේගවත් වෙනවා.
  • Functional Programming Support: Lambda expressions, Stream API වගේ දේවල් එක්ක වැඩ කරන්න ගොඩක් ලේසියි.

හරි, දැන් අපි බලමු කොහොමද Spring WebFlux project එකක් පටන් ගන්නේ කියලා. ඔයාලට පුලුවන් Spring Initializr (start.spring.io) එකෙන් project එකක් generate කරගන්න. Dependencies විදියට Spring Reactive Web එක තෝරන්න අමතක කරන්න එපා. ඒකෙන් Reactor core dependencies සහ Netty server එක automatically ඇඩ් වෙනවා.


// build.gradle
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
    // Other dependencies like R2DBC for reactive database access if needed
}

Reactive REST Endpoint එකක් හදමු

දැන් අපි බලමු කොහොමද Reactive REST Endpoint එකක් හදන්නේ කියලා. අපි හිතමු අපිට User Management System එකක් තියෙනවා කියලා. අපිට Users ටික retrieve කරන්න ඕන.

මුලින්ම අපි User Model එක හදමු:


// User.java
package com.example.webfluxdemo.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data // Generates getters, setters, toString, equals, hashCode
@AllArgsConstructor // Generates a constructor with all fields
@NoArgsConstructor // Generates a no-argument constructor
public class User {
    private String id;
    private String name;
    private int age;
}

ඊළඟට අපි Service Layer එකක් හදමු. මේකේදී අපි database එකක් පාවිච්චි කරන්නේ නැතුව, dummy data ටිකක් generate කරලා දෙමු. හැබැයි මේක Reactive විදියට.


// UserService.java
package com.example.webfluxdemo.service;

import com.example.webfluxdemo.model.User;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

@Service
public class UserService {

    private final List<User> users = new ArrayList<>();

    public UserService() {
        // Adding some dummy users
        users.add(new User(UUID.randomUUID().toString(), "Kasun Perera", 30));
        users.add(new User(UUID.randomUUID().toString(), "Amali Silva", 25));
        users.add(new User(UUID.randomUUID().toString(), "Nimal Fernando", 35));
        users.add(new User(UUID.randomUUID().toString(), "Kamala Bandara", 28));
        users.add(new User(UUID.randomUUID().toString(), "Sameera Jayasundara", 32));
    }

    public Flux<User> getAllUsers() {
        // Simulating a delay to show reactive benefits (e.g., fetching from a slow external API)
        // Returns a stream of users
        return Flux.fromIterable(users)
                .delayElements(Duration.ofSeconds(1)); // Emit one user per second
    }

    public Mono<User> getUserById(String id) {
        // Returns a single user or an empty Mono if not found
        return Mono.justOrEmpty(users.stream()
                .filter(user -> user.getId().equals(id))
                .findFirst());
    }

    public Mono<User> createUser(User newUser) {
        newUser.setId(UUID.randomUUID().toString()); // Assign a new ID
        users.add(newUser);
        return Mono.just(newUser); // Return the created user as a Mono
    }
}

මේකේදී අපි `Flux.fromIterable(users)` පාවිච්චි කරලා List එකක් Flux එකකට convert කරා. `delayElements(Duration.ofSeconds(1))` දාලා තියෙන්නේ reactive stream එකේ ලස්සන බලන්න. එක User කෙනෙක්ව second එකෙන් second එකට stream කරනවා වගේ. ඒ වගේම `Mono.justOrEmpty` පාවිච්චි කරලා Optional එකක් Mono එකකට convert කරලා තියෙනවා.

අන්තිමට, අපි Controller එක හදමු:


// UserController.java
package com.example.webfluxdemo.controller;

import com.example.webfluxdemo.model.User;
import com.example.webfluxdemo.service.UserService;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

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

    private final UserService userService;

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

    @GetMapping(produces = MediaType.TEXT_EVENT_STREAM_VALUE) // For streaming data as server-sent events
    public Flux<User> getAllUsersStream() {
        System.out.println("Getting all users stream...");
        return userService.getAllUsers();
    }

    @GetMapping
    public Flux<User> getAllUsers() {
        System.out.println("Getting all users...");
        return userService.getAllUsers().log(); // .log() is useful for debugging reactive streams
    }

    @GetMapping("/{id}")
    public Mono<User> getUserById(@PathVariable String id) {
        System.out.println("Getting user by ID: " + id);
        return userService.getUserById(id);
    }

    @PostMapping
    public Mono<User> createUser(@RequestBody User user) {
        System.out.println("Creating new user: " + user.getName());
        return userService.createUser(user);
    }
}

මේ Controller එකේදී, `getAllUsersStream()` method එකෙන් `MediaType.TEXT_EVENT_STREAM_VALUE` return කරනවා. මේක server-sent events (SSE) කියන concept එක. Client එකට පුලුවන් stream එක විදියට data එනකම් බලන් ඉන්න. Browser එකක `http://localhost:8080/api/users` (for `Flux` response) or `http://localhost:8080/api/users/stream` (for `TEXT_EVENT_STREAM_VALUE` to see data stream live) ට ගිහින් බලන්න. (or use curl `curl http://localhost:8080/api/users` ) හැබැයි stream එකේ බලන්න නම් `produces = MediaType.TEXT_EVENT_STREAM_VALUE` එකට අදාල Endpoint එකට යන්න ඕන. `delayElements` නිසා එක user කෙනෙක් විතරක් තත්පරයකට පාරක් එනවා දකින්න පුලුවන්. ඒ වගේම `Mono` එකක් return කරන `getUserById` method එක. ඒ වගේම `createUser` කියන `POST` method එකත් `Mono<User>` එකක් return කරනවා. මේ හැම එකක්ම Non-Blocking විදියට තමයි වැඩ කරන්නේ.

WebClient – Reactive Client එකක්

අපි දැන් server side එක reactive කරා. හැබැයි client side එකත් reactive නම් වැඩේ තවත් ලේසියි. Spring WebFlux වලට WebClient කියන reactive, non-blocking HTTP client එකත් ඇතුලත්. සාමාන්‍ය RestTemplate එක blocking නිසා, reactive application එකක් ඇතුලේ ඒක පාවිච්චි කරන එක තේරුමක් නෑ. ඒ වෙනුවට WebClient එක පාවිච්චි කරන්න පුලුවන්.


// Example of using WebClient
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

public class UserClient {

    private final WebClient webClient;

    public UserClient() {
        this.webClient = WebClient.builder()
                .baseUrl("http://localhost:8080/api/users")
                .build();
    }

    public Flux<User> getAllUsers() {
        return webClient.get()
                .uri("/")
                .retrieve()
                .bodyToFlux(User.class); // Get a stream of Users
    }

    public Mono<User> getUserById(String id) {
        return webClient.get()
                .uri("/{id}", id)
                .retrieve()
                .bodyToMono(User.class); // Get a single User
    }

    public Mono<User> createUser(User user) {
        return webClient.post()
                .uri("/")
                .body(Mono.just(user), User.class)
                .retrieve()
                .bodyToMono(User.class); // Create a User
    }
}

WebClient එක පාවිච්චි කරන විදියත් ගොඩක්ම සරලයි. `retrieve()` method එකෙන් response එක retrieve කරනවා, ඊට පස්සේ `bodyToFlux()` හෝ `bodyToMono()` වලින් ඒක අපිට ඕන object type එකට convert කරනවා. මේකත් reactive විදියටම වැඩ කරන නිසා, client side එකෙනුත් non-blocking calls කරන්න පුලුවන්.

අවසන් වචනයක්...

ඉතින්, අද අපි කතා කරේ Spring WebFlux ගැන. Blocking I/O වලින් එන ගැටලු වලට කොහොමද Reactive Programming සහ Spring WebFlux වලින් විසඳුම් දෙන්නේ කියලා. ඒ වගේම අපි පොඩි reactive REST endpoint එකක් හදලත් බැලුවා. මේක පොඩි introduction එකක් විතරයි. WebFlux වල තව ගොඩක් දේවල් තියෙනවා – Functional Endpoints, R2DBC (Reactive Relational Database Connectivity), WebSockets වගේ දේවල්.

මේ Reactive Programming කියන concept එක මුලින්ම තේරුම් ගන්න ටිකක් අමාරු වෙන්න පුලුවන්. මොකද අපේ සාමාන්‍ය Imperative Programming style එකට වඩා ගොඩක් වෙනස් නිසා. හැබැයි එකපාරක් මේක තේරුම් ගත්තම, ඔයාලට පුලුවන් high-performance, scalable applications හදන්න. විශේෂයෙන්ම Microservices architecture එකක, මේ WebFlux වගේ දේවල් ගොඩක් වැදගත් වෙනවා.

මම හිතනවා මේ ලිපිය ඔයාලට Spring WebFlux ගැන හොඳ අදහසක් දෙන්න ඇති කියලා. ඔයාලත් මේක practice කරලා බලන්න. පොඩි project එකක් හදලා, Mono සහ Flux එක්ක වැඩ කරන්න පුරුදු වෙන්න. මොනවා හරි ප්‍රශ්න තියෙනවා නම්, comment section එකේ අහන්න. ඔයාලගේ අදහස්, අත්දැකීම් share කරන්නත් අමතක කරන්න එපා. අපි තවත් මේ වගේම සුපිරි concept එකක් අරගෙන ඊළඟ ලිපියෙන් හම්බවෙමු! තව සුපිරි guides එක්ක ඉක්මනටම එනවා. Stay tuned!