Kotan Code 枯淡コード

In search of simple, elegant code

Menu Close

Consuming a REST (micro) Service with Akka HTTP

In a recent blog post, I talked about how we could quickly and easily pull in all the bootstrapping necessary to fire up an HTTP server and create an Akka HTTP micro service. In this blog post, I’m going to walk you through using the same Akka HTTP library to consume a service.

First, let’s set up a flow that starts with an HttpRequest and finishes with an HttpResponse:

lazy val zombieConnectionFlow: Flow[HttpRequest, HttpResponse, Any] =
    Http().outgoingConnection("localhost", 9001)

This sets up a connection flow from the client (which can be a full app, or in many cases, another micro service) pointing at http://localhost:9001. Note that you can add many more options to the Http() builder syntax to set up things like authentication, SSL, etc.

Once we have a flow, we need a way to convert requests into responses:

def zombieRequest(request:HttpRequest): Future[HttpResponse] = 
    Source.single(request).via(zombieConnectionFlow).runWith(Sink.head)

This function takes an HttpRequest and uses the Akka HTTP scala DSL to instruct Akka HTTP in how to complete that request. In our case, we create a single request and transmit it via our zombieConnectionFlow.

Now that we’ve got the plumbing (a flow and a request handler) set up, we can create a simple function that will consume our zombie micro service, fetching a single zombie by ID:

def fetchZombieInfo(id: String) : Future[Either[String, Zombie]] = {
 zombieRequest(RequestBuilding.Get(s"/zombies/$id")).flatMap { response =>
   response.status match {
     case OK => Unmarshal(response.entity).to[Zombie].map(Right(_))
     case BadRequest => Future.successful(Left(s"bad request"))
     case _ => Unmarshal(response.entity).to[String].flatMap { entity => 
       val error = s"FAIL - ${response.status}"
       Future.failed(new IOException(error))
     }
   }
 }
}

There are a couple of really important things to note here. The first is that when invoking my zombieRequest method, I am using just the REST API spec – there’s no URL used here – that was abstracted earlier as part of the flow.

The potential here is enormous. With Akka HTTP, we no longer have to string together a bunch of repetitive, imperative-looking statements to manifest a client that consumes another service. Instead, we can declare our intended flow, define a bunch of requests that execute over that flow, and then pattern match the results of invoking those requests.

Finally, with the fetchZombieInfo method created, we can expose that in our own micro service route (assuming, for the sake of example, that we had augmented the other micro service and we weren’t just proxying here):

pathPrefix("zombieproxy") {
  (get & path(Segment)) { zombieId =>
     complete {
       fetchZombieInfo(zombieId).map[ToResponseMarshallable] {
         case Right(zombie) => zombie
         case Left(errorMessage) => BadRequest -> errorMessage
       }
     }
   }
 }

While I personally feel that the convention of using the left side of Scala’s Either type is prejudiced against those of us who are left-handed, I can understand where the convention started.

So now if we issue the following curl:

curl http://localhost:9001/zombieclient/spongebob

It will go through our proxy and consume the micro service we wrote the other day, and return the single zombie we’re looking for:

{
 "name": "spongebob",
 "speed": 12
}

I am a huge fan of a number of HTTP client libraries. Lately, my favorite has been OK HTTP, for a number of reasons. It provides a very clean, simple, easy-to-use syntax for performing relatively complex HTTP operations. Also, it’s available automatically for Android, which allows me to write Java code that consumes services that is identical on my Android and server platforms.

As much as I love that library, Akka HTTP is my new favorite HTTP client. There are a pile of reasons, but I think the biggest is that Akka HTTP provides the smallest impedance mismatch between what I want to do over HTTP and how I write code to accomplish that.

In every project I have created in the past that had a component that consumed other services, the service client code rapidly became bloated, confusing, and covered in scar tissue. I actually feel like putting a little thought into creating a layer atop Akka HTTP to consume services could prevent that and still allow us to use futures and remain within the confines of the “reactive manifesto”.