Slimer
Slimer

Reputation: 1163

Reactor on-demand flux or a sink

Consider a HTTP controller endpoint that accepts requests, validates and then returns ack, but in a meantime in a background does some "heavy work".

There are 2 approaches with Reactor (that I'm interested in) that you can achieve this:

First approach

@PostMapping(..)
fun acceptRequest(request: Request): Response {
  if(isValid(request)) {
    Mono.just(request)
      .flatMap(service::doHeavyWork)
      .subscribe(...)
    return Response(202)
  } else {
    return Response(400)
  }
}

Second approach

class Controller {
  private val service = ...
  private val sink = Sinks.many().unicast().onBackpressureBuffer<Request>()
  private val stream = sink.asFlux().flatMap(service::doHeavyWork).subscribe(..)


  fun acceptRequest(request: Request): Response {
    if(isValid(request)) {
      sink.tryEmitNext(request) // for simplicity not handling errors
      return Response(202)
    } else {
      return Response(400)
    }
  }
}

Which approach is better/worse and why?

The reason I'm asking is, that in Akka, building streams on demand was not really effective, since the stream needed to materialize every time, so it was better to have the "sink approach". I'm wondering if this might be a case for reactor as well or maybe there are other advantages / disadvantages of using those approaches.

Upvotes: 2

Views: 1754

Answers (1)

Michael Berry
Michael Berry

Reputation: 72254

I'm not too familiar with Akka, but building a reactive chain definitely doesn't attract a huge overhead with Reactor - that's the "normal" way of handling a request. So I don't see the need to use a separate sink like in your second approach - that just seems to be adding complexity for little gain. I'd therefore say the first approach is better.

That being said, generally, subscribing yourself as you do in both examples isn't recommended - but this kind of "fire and forget" work is one of the few cases it might make sense. There's just a couple of other potential caveats I'd raise here that may be worth considering:

  • You call the work "heavy", and I'm not sure if that means it's CPU heavy, or just IO heavy or takes a long time. If it just takes a long time due to firing off a bunch of requests, then that's no big deal. If it's CPU heavy however, then that could cause an issue if you're not careful - you probably don't want to execute CPU heavy tasks on your event loop threads. In this case, I'd probably create a separate scheduler backed by your own executor service, and then use subscribeOn() to offload those CPU heavy tasks.
  • Remember the "fire and forget" pattern in this case really is "forget" - you have absolutely no way of knowing if the heavy task you've offloaded has worked or not, since you've essentially thrown that information away by self-subscribing. Depending on your use-case that might be fine, but if the task is critical or you need some kind of feedback if it fails, then it's worth considering that may not be the best approach.

Upvotes: 1

Related Questions