Be Proactive, Not Reactive

Be Reactive!

Rob Bosman    21-02-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:

  • Hashtable

  • Semaphore

  • synchronized

  • volatile

  • Runnable

  • ThreadPool

  • ThreadLocal

  • InterruptedException

Multithread programming

since Java 5:        

  • ConcurrentHashMap

  • fork / join

  • Future

private fun findTheAnswer(): Int {
  log.debug("pondering...")
  Thread.sleep(1000)
  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
}

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?

Golem

Rabbi Jehoeda Löw

Functor

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:Obj(C)\rightarrow Obj(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 jokeRawO = Observable
      .create<String> { emitter -> emitter.onNext(fetchJoke()) }
      .subscribeOn(Schedulers.io())

  Observable
      .create<MongoClient> { emitter -> emitter.onNext(getMongoClient()) }
      .subscribeOn(Schedulers.io())
      .zipWith(jokeRawO,
          BiFunction { mongoClient: MongoClient, jokeRaw: String ->
            val joke = convertAndStore(jokeRaw, 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(3000, MILLISECONDS)
  log.debug("there you are!")
}
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 { jokeRaw -> if (!jokeRaw.contains("success"))
          throw Throwable("invalid data: $jokeRaw") }
      .doOnError { t -> log.warn("error detected: '${t.message}'", t) }
      .retry(3)
      .onErrorResumeNext(Single.just("fallback joke"))
      .doFinally { processControl.release() }
      .subscribe(
          { jokeRaw -> log.info("'$jokeRaw'") },
          { t -> log.error("an ERROR occurred", t) }
      )

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

Software engineer

  • highly skilled craftsman

  • uses advanced tools

  • creative

  • bug fixer

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

Single instead of Observable?

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 { jokeRaw -> if (!jokeRaw.contains("success"))
          throw Throwable("invalid data: $jokeRaw") }
      .doOnError { t -> log.warn("error detected: '${t.message}'", t) }
      .retry(3)
      .onErrorResumeNext(Single.just("fallback joke"))
      .doFinally { processControl.release() }
      .subscribe(
          { jokeRaw -> log.info("'$jokeRaw'") },
          { t -> log.error("an ERROR occurred", t) }
      )

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

Recall two distinct phases:

  • preparation

  • execution

  • RxJava

  • Vert.x

  • Project Reactor

  • Java 9 Flow API

Resilience

dealing with errors

Separate (async) flows for

  • messages

  • Exceptions

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 { jokeRaw -> if (!jokeRaw.contains("success"))
          throw Throwable("invalid data: $jokeRaw") }
      .doOnError { t -> log.warn("error detected: '${t.message}'", t) }
      .retry(3)
      .onErrorResumeNext(Single.just("fallback joke"))
      .doFinally { processControl.release() }
      .subscribe(
          { jokeRaw -> log.info("'$jokeRaw'") },
          { t -> log.error("an ERROR occurred", t) }
      )

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

Message Driven

request - reply (sync)

request - reply (async)

Vert.x

  • library

  • message driven

  • Verticles

  • non-blocking

  • integrates with Rx

  • rich API

  • polyglot

  • well documented

Vert.x

fun main() {
  val vertx = Vertx.vertx()

  CompositeFuture
      .all(
          CompositeFuture.all(
              deployVerticle(vertx, PeanutPooper::class.java.name),
              deployVerticle(vertx, Chocolatifier::class.java.name),
              deployVerticle(vertx, Painter::class.java.name),
              deployVerticle(vertx, LetterStamper::class.java.name),
              deployVerticle(vertx, MnMPackager::class.java.name)),
          CompositeFuture.all(
              deployVerticle(vertx, PeanutSpeedLogger::class.java.name),
              deployVerticle(vertx, HttpEventServer::class.java.name))
      )
      .setHandler { result ->
        if (result.succeeded()) {
          log.info("We have hyperdrive, captain.")
        } else {
          log.error("Error", result.cause())
        }
      }
}

demo time!

Chocolate factory

One more thing...

Kotlin coroutines and Java fibers


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

GitHub: Cerios/

ReactiveChocolate

Made with Slides.com