reactor / reactor-addons

Additional optional modules for the Reactor project

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Ability to setup "transformation" based on the first element in the stream

OlegDokuka opened this issue · comments

As we came up in our daily development, it might be useful to choose routing of the stream, or simply name it transformation under the stream based on the first element in this stream:

Use-cases:

The following is a list of use-cases for in which this operator might be useful and simplify daily development:

Selecting routing based on the first frame in the stream

Suppose we have a stream of data from the client to the server over TCP channel, and once we established a connection, we start receiving frames. In many cases, we need to select a routing path depends on the headers frames, which is usually first, so the usage of such an operator might look like the following:

Map<String, Handler> handlersMap = ...;
FluxAddons
   .from(flux)
   .switchTransform((headerFrame, flux) -> {
      Handler handler = handlersMap.get(getKeyFromFrame(headerFrame));
      return handler.handle(flux) // return Publisher here
   });

As we can see from the example above, basing on the first element in the stream we make a decision how should we handle that stream of the element at all.

Of course, one may argue that we may achieve the same with the pure reactor. However, in that case, the code will be unreadable and less efficient:

flux.publish(f -> {
  return f
    .next()
    .flatMapMany(e -> {
       Handler handler = handlersMap.get(getKeyFromFrame(headerFrame));
       return handler.handle(f.startWith(e));
    });
})

In the case shown above, we will get ~2times degradation in performance (because of FluxPublish operator and SpScQueue under the hood) plus unclear behavior of what is going on there, where implementation of the switchTransform involves only a few atomic operators which is a common part of every operator in Reactor

Element processing based on the initial (first) element

Suppose that we have a process of decision making based on the first currency value within a time window. In that case, as in the example above, we will have to do sum unclear transformation in order to achieve the first element in the stream an use it for doing the math on new elements within the time window. With switch transform we may solve it in a few transformations:

fluxCCY
   .window(Duration.ofHours(1))
   .flatMap(fluxCCYInWindow -> 

      FluxAddons
         .from(fluxCCYInWindow)
         .switchTransform((initialCCY, f) -> f.skip(1).map(ccy -> ccy/initialCCY));

Conclusion

As we can see from the examples, this operator may be useful in many use-cases. The application of the operator is not limited to the samples above and could be used in order to solve many unordinary problems

@smaldini we discussed this type of operator once, but can't remember when.

@akarnokd we have draft impl there -> https://github.com/rsocket/rsocket-java/blob/1.0.x/rsocket-core/src/main/java/io/rsocket/internal/SwitchTransformFlux.java

But I'm not sure I'm correctly implemented cancelation, so it would be amazing if take a look at that ❤️

plus, after implementing the same for js -> https://github.com/rsocket/rsocket-rpc-js/pull/8/files#diff-11540015383e0735eba0d37955e91a2bR29, I'm not 100% sure that we need Main + Inner combination and can reduce -1 object for GC, so in any case, @akarnokd your feedback will be amazing

@akarnokd @OlegDokuka I think this operator is needed in core in fact, we have a need e.g. in Spring to lazily evaluate the first signal to reroute a different flux then (for sending http 500 status instead of whatever user status is configured).

I would just change the first argument T as a Signal to include errors and completion (source emptiness)

Would you mind opening that issue on core @OlegDokuka ? Sorry for the bouncing between the 2 projects.