Logo

dev-resources.site

for different kinds of informations.

Future Composition in Vert.x

Published at
5/20/2020
Categories
vertx
async
java
Author
ndrbrt_23
Categories
3 categories in total
vertx
open
async
open
java
open
Author
9 person written this
ndrbrt_23
open
Future Composition in Vert.x

For those who don't know, Vert.x is an event driven and non blocking application toolkit.
It's polyglot, so you can use it with different languages (as Java, Kotlin, JavaScript, Groovy, Ruby or Scala).

What does "non blocking" mean?

In synchronous programming, when a function is called, the caller has to wait until the result is returned.
This behaviour could lead to performance issues.

Often the "obvious solution" seems to be concurrent programming, but dealing with shared resources and threads is not easy and deadlocks are around the corner.

Explaining how Vert.x guarantees asynchrony through the event loop concept is not in the scope of this article, but anything you may want to know gets unraveled in the great Gentle guide to async programming with Vert.x.

In a "non blocking" world, when the result of a function can be provided immediately, it will be. Otherwise a handler is provided to handle it when it will be ready.

asyncFunctionCall(result -> {
    doSometingWith(result);
})

Usualy a big "but" gets raised the first time an async piece of code is seen: But... I need that result now!

This kind of programming requires a mindset change: it's necessary to know and understand the main patterns.

Futures vs Callbacks

There are two ways to deal with async calls in Vert.x.

  • Pass a callback that will be executed on call completion.
  • Handle a future returned from the function/method call.

Callback

asyncComputation(asyncResult -> {
  if (asyncResult.succeeded()) {
    asyncResult.result()
    // do stuff with result
  } else {
    // handle failure
  }
})

A callback is a function passed to an async method used to handle the result of its computation.
It's simple to implement but it brings some drawbacks:

  • Unit testing becomes not-so-immediate
  • Leads to the dreaded Callback Hell
  • There's more code to be written.

Future

Future future = asyncComputation()

future.onSuccess(result -> {
  // do stuff
})

future.onFailure(cause -> {
  // handle failure
})

To avoid the problems listed for the "callback way", Vert.x implements a concept called Future.

A future is an object that represents the result of an action that may, or may not, have occurred yet (cf. apidoc).

How to switch from a callback-like call to a future one

Consider the callback example shown above.
We want a Future object to take advantage of the patterns described below, but asyncComputation is a function from another library, so we cannot modify it.

We can use a Promise.
According to the apidoc, it represents the writable side of an action that may, or may not, have occurred yet.
It perfectly fits our needs:

Promise promise = Promise.promise();
asyncComputation(asyncResult -> {
  if (asyncResult.succeeded()) {
    promise.complete(asyncResult.result());
  } else {
    promise.fail(asyncResult.cause());
  }
})
return promise.future()

That's it. We transformed a callback into a future.
The Promise API's give us a way to make this code more readable:

Promise promise = Promise.promise();
asyncComputation(promise::handle)
return promise.future()

The handle method takes care of the completion or failure of the promise, given an async result.

Future patterns

The Future object implements some interesting patterns that smartly help resolving async issues:

  • Map
  • Compose
  • Recover

Future Mapping

For those of you who know about the map function, part of the java Stream API, this feature should be immediate to understand.

The Future's map function accepts a function that transforms the result from one type to another.

asyncComputation() // returns a Future<String>
  .map(Integer::valueOf) // returns a Future<Integer>
  .onSuccess(...)
  .onFailure(...)

Future Composition

The compose method is similar to map, but is used when the mapping is an async operation itself:

asyncComputation()
  .map(Integer::valueOf)
  .compose(id -> retrieveDataById(id)) // retrieveDataById returns a Future
  .onSuccess(...)
  .onFailure(...)

Future Recovery

Futures can succeed, but can also fail.
To handle a failed future and consider a different behaviour, recover function can be used:

asyncComputation()
  .map(Integer::valueOf)
  .recover(cause -> Future.succeededFuture(0)) // when Integer::valueOf fails, the future could be recovered with a default value
  .compose(id -> retrieveDataById(id))
  .onSuccess(...)
  .onFailure(...)

Concurrent composition

To handle multiple future results at the same time, CompositeFuture is the class needed, it provides two static factory methods:

all returns a future that succeeds if all the futures passed as parameters succeed, and fails if at least one fails.

CompositeFuture.all(futureOne, futureTwo)
  .onSuccess(compositeResult ->
    // all futures succeeded
  )
  .onFailure(cause -> 
    // at least one failed
  );

any returns a future that succeeds if any one of the futures passed as parameters succeed, and fails if all fail.

CompositeFuture.any(futureOne, futureTwo)
  .onSuccess(compositeResult ->
    // at least one succeed
  )
  .onFailure(cause -> 
    // all failed
  );

Conclusions

Future composition API in Vert.x represents a solid way to write simple and affordable async code.
Always remember:

  • never throw exceptions into async code, use failed future instead to handle failure behaviours.
  • at the end of a future composition, don't forget to handle future's successes (onSuccess) and failures (onFailure)
vertx Article's
28 articles in total
Favicon
Error handlers and failure handlers in Vert.x
Favicon
Why we discarded Reactive systems architecture from our code?
Favicon
Build web application in Vert.x [Part 1/ โ™พ๏ธ]
Favicon
Yet another ode to Vert.x, or how to write a performance-wise expiring map in less than 100 lines of code.
Favicon
Surprising Qualities of Event Driven System
Favicon
Idiomatic Kotlin Abstractions for the Vert.x EventBus
Favicon
Vert.x Circuit Breaker
Favicon
Writing Async Tests for Vert.x using Kotlin
Favicon
Reducing Boilerplate in Vert.x Tests written in Kotlin
Favicon
Writing Vert.x Integration Tests with Kotlin & Testcontainers
Favicon
Quarkus: Entendendo a relaรงรฃo entre o Mutiny e o Vert.x
Favicon
HTTPS Client Certificate Authentication With Java
Favicon
Throttle HTTP requests on paged resources with Vert.x
Favicon
Supercharge Your Kotlin Vert.x Application with EventBus Extensions
Favicon
Handle backpressure between Kafka and a database with Vert.x
Favicon
Handling unknown JSON structures
Favicon
Introduction to Vert.x
Favicon
Future Composition in Vert.x
Favicon
How to extend Vert.x EventBus API to save on serialization.
Favicon
How to write beautiful unit tests in Vert.x
Favicon
Scaling Vert.x application for session dependent data processing.
Favicon
KVision v3.7.0 is released (with Vert.x support)
Favicon
Reactive Java using the Vert.x toolkit
Favicon
Vert.x Kotlin Coroutines
Favicon
How we built a RESTful API with Vert.x, Kotlin Coroutines and Keycloak
Favicon
vertx-jooq 2.4 released
Favicon
Sirix - Released 0.9.1 (time travel queries and versioning made easy)
Favicon
Reactive Programming with Kotlin - Quick Intro to Vert.x

Featured ones: