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.
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.

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
Be Reactive!
By Rob Bosman
Be Reactive!
- 98