Class Experiment03_Flow

java.lang.Object
net.kolotyluk.loom.Experiment03_Flow

public class Experiment03_Flow extends Object

Flow Experiments

Flow is Java's default Reactive Streams framework that addresses the Producer–Consumer Problem by restricting how many messages the Producer (Publisher) can send the Consumer (Subscriber), such that the Producer will block when the Consumer cannot consume any more messages, using a technique called Back Pressure.

Why Loom

As we have learned before, using Java Parallel Streams with Transactional Tasks is problematic, where Virtual Threads handle Transactional Tasks better than Platform Threads. So, in terms of a classic Producer/Consumer application, where the Producers and/or the Consumers are Transactional, using Virtual Threads can be an effective improvement over regular thread pools.

Why Flow

If Project Loom solves several problems that were solved by Reactive Programming methods, why do we want to continue using Reactive Programming methods such as Reactive Streams?

  1. Given there is an existing and growing ecosystem of Reactive Solutions, we will have to go with the momentum for a while.
  2. It is expected that most Reactive Frameworks will refactor their code to use Project Loom capabilities to exploit the obvious advantages, such as support in the JVM.
  3. Backpressure is a really valuable technique for some applications, and that is not a feature of Project Loom.

Increasingly many APIs are providing both Synchronous and Asynchronous capabilities to better handle the reality of asynchronous and concurrent programming practices. In some case, an API will return a Future or CompletableFuture as a result, and it is up to the API User to best decide how to consume that. Increasingly, however, many APIs are now returning a Flow.Publisher, or taking one as an argument to the API; similarly for Flow.Subscriber. Such patterns are often useful when the data sets are large, and/or dynamically generated. For example:

 var myPublisher  = new MyPublisher();  // produces an HTTP Request  Body
 var mySubscriber = new MySubscriber(); // consumes an HTTP Response Body

 var request = HttpRequest.newBuilder()
     .uri(URI.create("https://www.boredapi.com/api/activity"))
     .POST(myPublisher)
     .build();

 client
     .sendAsync(request, info -> HttpResponse.BodySubscribers.ofPublisher())
     .thenAccept(action -> {action.body().subscribe(mySubscriber);})
     .join();

 // or

 CompletableFuture<HttpResponse<Flow.Publisher<List<ByteBuffer>>>> response =
     client.sendAsync(request, info ->
         HttpResponse.BodySubscribers.ofPublisher());

 response
     .thenAccept(action -> {action.body().subscribe(mySubscriber);})
     .join();
 

where myPublisher will produce the HTTP Request Body and mySubscriber will consume the HTTP Response Body. Note: I wanted to reveal how complex the result type of response would be. Yes, these are rather elaborate ways to send and receive HTTP bodies, but sometimes these kinds of data transport are non-trivial. Whether it be Request Body Production, or Response Body Consumption, we may want to maximize/optimize throughput and ideally both Flow and Virtual Threads can help.

In the world of HTTP/2 and HTTP/3, these protocols allow for the transport of very large payloads, and even dynamic streaming, where using Reactive Streams such as Flow can better produce, process, and consume such data. But, will Project Loom improve the throughput of these applications? However, we will explore this more in future experiments related to HTTP, because I just wanted to introduce one of the leading rationales for using Flow.

Publisher/Subscriber

However, Flow is also a Publish-Subscribe Pattern

See Also:
  • Constructor Details

    • Experiment03_Flow

      public Experiment03_Flow()
  • Method Details

    • main

      public static void main(String[] args)