Tuesday, September 2, 2014

Amazon AWS Async and Scala

Do you use Scala? how about AWS? Then you know that the Amazon AWS Java SDK comes with async variants of most of their methods. Unfortunately, they all return java Futures which are pretty useless. There's no easy or clean way to turn that java Future into a scala Future, since java doesn't provide the necessary methods. You can write some code to poll continuously, but that sort of negates the whole async thing.

Fortunately, their methods usually also include a version that takes an instance of AsyncHandler, which allows our code to remain truly async. But... what a pain... what we really want is a scala Future. One that you can map, flatMap, stick in a for-comprehension, etc. 

Lucky for us, there's a way to transform that AsyncHandler-taking-method into something more natural. And here it is:

class AwsAsyncPromiseHandler [R<:AmazonWebServiceRequest,T] (promise: Promise[T]) extends AsyncHandler [R,T] {
  def onError (ex: Exception) = promise failure ex
  def onSuccess (r: R, x: T) = promise success x
}
def awsToScala [R<:AmazonWebServiceRequest,T](fn: Function2[R,AsyncHandler[R,T],java.util.concurrent.Future[T]]): Function1[R,Future[T]] = {req =>
  val p = Promise[T]
  fn(req, new AwsAsyncPromiseHandler(p) )
  p.future
}
This is an example of how you use it:
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBAsyncClient
import com.amazonaws.services.dynamodbv2.model._
import com.gravitydev.awsutil.awsToScala
... 
// regular amazon request (dynamodb for this example)
val req = new GetItemRequest()
  .withTableName("users")
  .withKey("userid@somedomain.com")
  .withAttributesToGet(...)

// wrap the relevant '...Async' call with 'awsToScala' to convert the aws call from:
// someAwsMethod(request, callback) => unit 
// into a scala future
// type: Future[BatchGetItemResult]
val response = awsToScala(client.batchGetItemAsync)(req)

// do what you want with it now, asynchronously:
// response is a regular scala Future[BatchGetItemResult]
for (res <- response) yield {
  ...
}
You can copy it into your code, or if you want, you can get it from here:
Enjoy!