Be Proactive, Not Reactive

Be Reactive!

Rob Bosman    3-12-2019

  • Responsive
     

  • Resilient
     

  • Elastic
     

  • Message Driven

class FetchJokeService {

  private static final Logger LOG = LoggerFactory.getLogger(FetchJokeServiceJava.class);
  private static final String API_URL
      = "http://api.icndb.com/jokes/random?limitTo=[explicit,nerdy]"; // Chuck Norris jokes

  static String fetchJoke() {
    LOG.debug("fetch joke");
    try {
      final HttpURLConnection connection = (HttpURLConnection) new URL(API_URL).openConnection();
      try {
        try (final BufferedReader reader
                 = new BufferedReader(new InputStreamReader(connection.getInputStream(), UTF_8))) {
          return reader.lines().collect(joining());
        } catch (IOException e) {
          throw new RuntimeException("Error reading from InputStream", e);
        }
      } finally {
        connection.disconnect();
        LOG.debug("fetched joke");
      }
    } catch (MalformedURLException e) {
      throw new RuntimeException("Error in URL " + API_URL, e);
    } catch (IOException e) {
      throw new RuntimeException("Error connecting to " + API_URL, e);
    }
  }
}

IntelliJ IDEA

MongoDB

Don't block the main Thread

Multi Thread programming

before Java 5:

  • Runnable

  • ThreadPool

  • ThreadLocal

  • InterruptedException

  • Hashtable

  • Semaphore

  • synchronized

  • volatile

Multi Thread programming

since Java 5:        

  • ConcurrentHashMap

  • fork / join

  • Future

private fun findTheAnswer(): Int {
  log.debug("pondering...")
  Thread.sleep(1_000)
  return 42
}

fun backToTheFuture() {
  val executor = Executors.newFixedThreadPool(2)

  val future = executor.submit(::findTheAnswer)

  log.debug("are you done? - ${future.isDone}")

  // do other stuff

  val answer = future.get() // blocking wait
  log.debug("ah, the answer is $answer")

  executor.shutdown()
}

IntelliJ IDEA

fun backToTheCompletableFuture() {

  val provideAnswerCF = CompletableFuture

      .supplyAsync               {
                                   log.debug("determine required processing time")
                                   1500L
                                 }
      .thenApply                 { delayMillis ->
                                   log.debug("pondering...")
                                   Thread.sleep(delayMillis)
                                   42
                                 }
      .thenApply                 { answer ->
                                   log.debug("got it!")
                                   answer
                                 }

  log.debug("do you know the answer? - ${provideAnswerCF.isDone}")
  log.debug("ah, the answer is ${provideAnswerCF.get()}") // blocking wait
}

Two distinct 'flows':

  • preparation

  • execution

fun backToTheCompletableFuture() {

  val provideAnswerCF = CompletableFuture

      .supplyAsync {
        log.debug("determine required processing time")
        1500L
      }
      .thenApply { delayMillis ->
        log.debug("pondering...")
        Thread.sleep(delayMillis)
        42
      }
      .thenApply { answer ->
        log.debug("got it!")
        answer
      }

  log.debug("do you know the answer? - ${provideAnswerCF.isDone}")
  log.debug("ah, the answer is ${provideAnswerCF.get()}") // blocking wait
}
fun backToTheCompletableFuture() {

  val provideAnswerCF = CompletableFuture

      .supplyAsync {
        log.debug("determine required processing time")
        1500L
      }
      .thenApply { delayMillis ->
        log.debug("pondering...")
        Thread.sleep(delayMillis)
        42
      }
      .thenApply { answer ->
        log.debug("got it!")
        answer
      }

  log.debug("do you know the answer? - ${provideAnswerCF.isDone}")
  log.debug("ah, the answer is ${provideAnswerCF.get()}") // blocking wait
}

Functor

  • filter
  • transform (map)

Monad

  • is a Functor

  • flatten (flatMap)

 

A monad is just a monoid in the category of endofunctors, what's the problem?

fun backToTheCompletableFuture() {

  val provideAnswerCF = CompletableFuture

      .supplyAsync               {
                                   log.debug("determine required processing time")
                                   1500L
                                 }
      .thenApply                 { delayMillis ->
                                   log.debug("pondering...")
                                   Thread.sleep(delayMillis)
                                   42
                                 }
      .thenApply                 { answer ->
                                   log.debug("got it!")
                                   answer
                                 }

  log.debug("do you know the answer? - ${provideAnswerCF.isDone}")
  log.debug("ah, the answer is ${provideAnswerCF.get()}") // blocking wait
}

Functor

 

A morphism from a source category to a target category which maps objects to objects and arrows to arrows, in such a way as to preserve domains and codomains (of the arrows) as well as composition and identities.

F(f\circ g)=F(f)\circ F(g)=F(g)\circ F(f)
F:M_{C}(x,y)\rightarrow M_{D}(F(x),F(y))
(G\circ F)(f)=G(F(f))
F:Ob(C)\rightarrow Ob(D)

Golem

Rabbi Jehoeda Löw

Functor

 

A morphism from a source category to a target category which maps objects to objects and arrows to arrows, in such a way as to preserve domains and codomains (of the arrows) as well as composition and identities.

F(f\circ g)=F(f)\circ F(g)=F(g)\circ F(f)
F:M_{C}(x,y)\rightarrow M_{D}(F(x),F(y))
(G\circ F)(f)=G(F(f))
F:Ob(C)\rightarrow Ob(D)

Monads in Java 8

  • ​Stream
  • Optional
  • CompletableFuture
  • Rx Observable

ReactiveX Observable

  • lazy execution

  • push vs pull

  • hot vs cold

  • rich API

  • polyglot

  • well documented

  • version 1.0 vs 2.0

IntelliJ IDEA

fun run() {
  log.debug("here we go")
  val processControl = Semaphore(0)

  val jokeJsonO = Observable
      .create<String> { emitter -> emitter.onNext(fetchJoke()) }
      .subscribeOn(Schedulers.io())

  Observable
      .create<MongoClient> { emitter -> emitter.onNext(getMongoClient()) }
      .subscribeOn(Schedulers.io())
      .zipWith(jokeJsonO,
          BiFunction { mongoClient: MongoClient, jokeJson: String ->
            val joke = convertAndStore(jokeJson, mongoClient)
            mongoClient.close()
            log.debug("closed MongoDB client")
            joke
          })
      .subscribe { joke ->
        log.info("'$joke'")
        processControl.release()
      }

  log.debug("wait until all is done")
  processControl.tryAcquire(3_000, MILLISECONDS)
  log.debug("there you are!")
}

Resilience

Resilience is by design

Resilience

dealing with errors

Separate (async) flows for

  • messages

  • errors

goto(label);
throw new Exception();
fun backToResilience() {
  log.debug("here we go")
  val processControl = Semaphore(0)

  Single
      .create<String> { emitter -> emitter.onSuccess(fetchJokeFlaky()) }
      .timeout(200, MILLISECONDS)
      .subscribeOn(Schedulers.io())
      .doOnSuccess { jokeJson ->
        if (!jokeJson.contains("success"))
          throw Throwable("invalid data: $jokeJson")
      }
      .doOnError { t -> log.warn("error detected: '${t.message}'", t) }
      .retry(3)
      .onErrorResumeNext(Single.just("fallback jokeJson"))
      .doFinally { processControl.release() }
      .subscribe(
          { jokeJson -> log.info("'$jokeJson'") },
          { t -> log.error("an ERROR occurred", t) })

  log.debug("wait a second...")
  processControl.tryAcquire(1_000, MILLISECONDS)
  log.debug("there you are!")
}

IntelliJ IDEA

fun backToResilience() {
  log.debug("here we go")
  val processControl = Semaphore(0)

  Single
      .create<String>       { emitter -> emitter.onSuccess(fetchJokeFlaky()) }
      .timeout(             200, MILLISECONDS)
      .subscribeOn(         Schedulers.io())
      .doOnSuccess          { jokeJson ->
                                if (!jokeJson.contains("success"))
                                  throw Throwable("invalid data: $jokeJson")
                            }
      .doOnError            { t -> log.warn("error detected: '${t.message}'", t) }
      .retry(               3)
      .onErrorResumeNext(   Single.just("fallback jokeJson"))
      .doFinally            { processControl.release() }
      .subscribe(
                            { jokeJson -> log.info("'$jokeJson'") },
                            { t -> log.error("an ERROR occurred", t) })

  log.debug("wait a second...")
  processControl.tryAcquire(1_000, MILLISECONDS)
  log.debug("there you are!")
}

Recall two distinct phases:

  • preparation

  • execution

fun backToResilience() {
  log.debug("here we go")
  val processControl = Semaphore(0)

  Single
      .create<String> { emitter -> emitter.onSuccess(fetchJokeFlaky()) }
      .timeout(200, MILLISECONDS)
      .subscribeOn(Schedulers.io())
      .doOnSuccess { jokeJson ->
        if (!jokeJson.contains("success"))
          throw Throwable("invalid data: $jokeJson")
      }
      .doOnError { t -> log.warn("error detected: '${t.message}'", t) }
      .retry(3)
      .onErrorResumeNext(Single.just("fallback jokeJson"))
      .doFinally { processControl.release() }
      .subscribe(
          { jokeJson -> log.info("'$jokeJson'") },
          { t -> log.error("an ERROR occurred", t) })

  log.debug("wait a second...")
  processControl.tryAcquire(1_000, MILLISECONDS)
  log.debug("there you are!")
}

Software engineer

  • highly skilled craftsman

  • advanced tooling

  • creative

  • bug fixer

  • about estimates:
    "You can't rush art."

Message Driven

request - reply (sync)

request - reply (async)

Standard for
async stream processing with
non-blocking back pressure

 

  • RxJava

  • Vert.x

  • Project Reactor

  • Java 9 Flow API

github.com/
RobBosman/
BeReactive

One more thing...

Kotlin coroutines (and Java fibers)

import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlin.concurrent.thread

internal object ParallelProcessingTest {

  @Test
  fun withThreads() {
    val startNano = System.nanoTime()
    val endNano = AtomicLong()
    val count = AtomicInteger()

    IntStream.range(0, NUM_PARALLEL_PROCESSES)
        .forEach {

          thread(start = true) {
            log.debug("performing task in thread")
            Thread.sleep(1_000)
            if (count.incrementAndGet() == NUM_PARALLEL_PROCESSES)
              endNano.set(System.nanoTime())
          }
        }

    while (endNano.get() == 0L) Thread.yield()
    log.debug("${count.get()} Thread-tasks took ${endNano.get().minus(startNano) / 1_000_000} ms")
  }
}

IntelliJ IDEA

github.com/
RobBosman/
BeReactive

Made with Slides.com