Re: [akka-user] Composing services that return Future[Either] (or Future of any monad)

170 views
Skip to first unread message

√iktor Ҡlang

unread,
Mar 29, 2013, 9:40:28 PM3/29/13
to Akka User List
Hi Jack,

What is "Error"?

Cheers,


On Sat, Mar 30, 2013 at 2:35 AM, Jack Singleton <jack.s.s...@gmail.com> wrote:
This is the second time I've ran into this problem on a Play/Scala project. I feel like there should be an elegant solution but cannot find it.

Let's say we have many services that each return Future[Either[ErrorCase, Success]].

In this example, I have serviceA: Future[Either[Error, String]] and serviceB(string: String): Future[Either[Error, String]]

serviceB depends on the result of serviceA

import concurrent._
import duration._
import ExecutionContext.Implicits.global

case class Error(error: String)

def serviceA: Future[Either[Error, String]] = Future(Right("hello"))

def serviceB(string: String): Future[Either[Error, String]] = Future(Right(string + "world"))

val result: Future[Either[Error, Future[Either[Error, String]]]] = serviceA.map(_.right.map(string => serviceB(string)))

// This is painful
val string: Future[String] = result flatMap { eitherA =>
eitherA.fold(error => {
Future.successful("error: " + error.error)
}, futureB => {
futureB map { eitherB =>
eitherB.fold(error => {
"error: " + error.error
}, string => {
"success: " + string
})
}
})
}

// Obviously we don't actually block
println(Await.result(string, Duration.Inf))

The main problem is that we have to handle our error cases separately for each service call. There is no way to map/flatMap over the whole structure and handle all the error cases at once. 

It seems that Twitters Try/Future implementation makes it possible to flatMap over a Future[Try] in one go, which I believe would solve the problem above. It does not appear that the Scala/Akka Future and Try implementations act this way though.

Is there any better way to compose these Future[Either]'s ?

Would using Future[Try]'s open up a path to sane composition?

We don't really want to use exceptions because we will then lose the type safety of our error cases. The service methods would no longer define what they will do in the case of an error.

Jack Singleton

--
>>>>>>>>>> Read the docs: http://akka.io/docs/
>>>>>>>>>> Check the FAQ: http://akka.io/faq/
>>>>>>>>>> Search the archives: https://groups.google.com/group/akka-user
---
You received this message because you are subscribed to the Google Groups "Akka User List" group.
To unsubscribe from this group and stop receiving emails from it, send an email to akka-user+...@googlegroups.com.
To post to this group, send email to akka...@googlegroups.com.
Visit this group at http://groups.google.com/group/akka-user?hl=en.
For more options, visit https://groups.google.com/groups/opt_out.
 
 



--
Viktor Klang
Director of Engineering

Twitter: @viktorklang

√iktor Ҡlang

unread,
Mar 29, 2013, 10:00:59 PM3/29/13
to Akka User List
I'm not sure I understood the reason for not using Exceptions for these exceptional cases, could you elaborate?

Cheers,


On Sat, Mar 30, 2013 at 2:48 AM, Jack Singleton <jack.s.s...@gmail.com> wrote:
Error would be one of our own classes and would vary depending on what went wrong. Basically signify that he could not compute a result, and provide context as to why not.

It could signify anything from an external service not being available (more on the exceptional side), to an entity or result set that does not exist.

Thanks

Jack Singleton

√iktor Ҡlang

unread,
Mar 29, 2013, 10:33:55 PM3/29/13
to Akka User List



On Sat, Mar 30, 2013 at 3:17 AM, Jack Singleton <jack.s.s...@gmail.com> wrote:
It's definitely an option we are weighing.

The main thing is that our error cases would no longer be defined in the type signature of the service method.

True, but they never (well, unless in Java, where you can use checked exception of Throwable) are, as any method call can throw a fairly large number of exceptions that are not encoded in the type signature.
 
Using Either's, it's easy to state (and enforce) that serviceA could result in one of a few well defined ServiceAError's, in which the type signature would be Future[Either[ServiceAError, Foo]]. 
Any client of serviceA now knows that they have to handle any ServiceAError.

But how do they know what to do?
 

In this project that often means an AnticipatedFailure or an UnanticipatedFailure. These are defined as subtypes of a sealed trait so that any non-exhaustive pattern match will be caught at compile time.

How do they differ from other failures (exceptions)? Aren't all exceptions unanticipated, i.e. I wouldn't have called the service if I'd known that it'd fail?
 

We do not consider all of these error cases to be exceptional. In fact, we don't define any of them as Throwable. So having to do so for the sole purpose of using these classes with the Future error handling mechanisms is another reason we are hesitant to go that route.

In that case I'd use exceptions for the exceptionals and then use Either or roll my own ServiceResult Monad (result-biased).

Does that help?

Cheers,
 

Jack Singleton

√iktor Ҡlang

unread,
Mar 29, 2013, 10:52:07 PM3/29/13
to Akka User List
future flatMap {
  case e @ Error(…) => Future successful e
  case Result(future) => future
}

or?

Cheers,


On Sat, Mar 30, 2013 at 3:45 AM, Jack Singleton <jack.s.s...@gmail.com> wrote:
We actually do have our own 'ServiceResult' monad (only it's called 'Outcome'). I've been using Either so there was less overhead in the example.

The same problem applies. We end up with a Future[Outcome[F, [Future[Outcome[F, R]]]], and cannot simply flatMap the Future because there is an Outcome standing in the way of the nested Future. 

Jack Singleton

√iktor Ҡlang

unread,
Mar 30, 2013, 3:02:18 PM3/30/13
to Akka User List
Yeah, you'll have to repackage Error so its second parameter matches the one of the result future.
Sorry for missing that, it was like 4am.

Cheers,


On Sat, Mar 30, 2013 at 4:20 AM, Jack Singleton <jack.s.s...@gmail.com> wrote:
That leaves us with a Future[Object] as it is the only common type beween Error and Outcome.

Thanks for the quick replies by the way. The answer may in fact be that we cant use futures like this, and should just bite the bullet and use exceptions.

Jack Singleton

√iktor Ҡlang

unread,
Apr 2, 2013, 4:37:17 PM4/2/13
to Akka User List

Yep, I was going to suggest writing monad transformer (s) for this but it felt a bit "over the top".

Cheers, V

On Apr 2, 2013 10:02 PM, "Gideon de Kok" <gide...@gmail.com> wrote:
So you're basically trying to map / flatMap over a data structure Future, containing a new data structure Either, containing a value T as content for a successful result.
The map / flatMap functions will always point to the containing context within the Future, which points to a Either in this case. 

If you comfortably want to use the functionality (or contexts) of the Future and Either type together, there probably isn't a better solution then to create Monad Transformer like structure combining the two monads into one. Probably (too) complex stuff, but less painful then stair stepping...
Reply all
Reply to author
Forward
0 new messages