My old code looks something like below, where all db calls blocking.
I need help converting this over to using Futures.
def getUserPoints(username: String): Option[Long]
db.getUserPoints(username) match {
case Some(userPoints) => Some(userPoints.total)
case None => {
if (db.getSomething("abc").isEmpty) {
db.somethingElse("asdf") match {
case Some(pointId) => {
db.setPoints(pointId, username)
db.findPointsForUser(username)
}
case _ => None
}
} else {
db.findPointsForUser(username)
}
}
}
}
My new API is below where I am returning Futures.
db.getUserPoints(username: String): Future[Option[UserPoints]]
db.getSomething(s: String): Future[Option[Long]]
db.setPoints(pointId, username): Future[Unit]
db.findPointsForUser(username): Future[Option[Long]]
How can I go about converting the above to use my new API that uses futures.
I tried using a for-compr but started to get wierd errors like Future[Nothing].
var userPointsFut: Future[Long] = for {
userPointsOpt <- db.getUserPoints(username)
userPoints <- userPointsOpt
} yield userPoints.total
But it gets a bit tricky with all the branching and if clauses and trying to convert it over to futures.
I would argue that the first issue with this design is that the port of the blocking call to a
Future
should not wrap the Option type:The blocking call:
def giveMeSomethingBlocking(for:Id): Option[T]
Should become:def giveMeSomethingBlocking(for:Id): Future[T]
And not:def giveMeSomethingBlocking(for:Id): Future[Option[T]]
The blocking call give either a value
Some(value)
orNone
, the non-blocking Future version gives either aSuccess(value)
orFailure(exception)
which fully preserves theOption
semantics in a non-blocking fashion.With that in mind, we can model the process in question using combinators on
Future
. Let's see how:First, lets refactor the API to something we can work with:
Then, the process would look like:
Which type-checks correctly.
Edit
Given that the API is set in stone, a way to deal with nested monadic types is to define a MonadTransformer. In simple words, let's make
Future[Option[T]]
a new monad, let's call itFutureO
that can be composed with other of its kind. [1]And now we can re-write our process preserving the same structure as the future-based composition.
[1] with acknowledgements to http://loicdescotte.github.io/posts/scala-compose-option-future/