/ akka

Building REST APIs with Akka HTTP and Spray JSON

I have been building RESTful APIs in Scala from quite a while using Play Framework, AkkaHTTP, Spray and others. While Spray had been one of my first choices to build RESTful API's in the past in the Scala Ecosystem, the Akka toolkit has adopted the Spray framework to make it part of Akka Toolkit. At the time of writing this article, Spray has completed its project life-cycle and there is no active development on Spray, while Akka has been extended to include Akka HTTP, an Akka module inspired by Spray to provide low-level server and client APIs for distributed application development over HTTP.

In this article, I will walk you through the step by step process for how to create a RESTful application using Akka HTTP and non-blocking IO at the backend with support for JSON request/response.

Akka HTTP: What and Why

Spray, the "predecessor" of Akka HTTP was built using Akka Actors. Akka HTTP was re-designed to use the same APIs and ecosystems largely with few vital changes in order to boost performance. Akka HTTP provides full server and client side HTTP stack built on top of akka-stream and akka-actor. The re-design of Spray as Akka HTTP was primarily motivated by akka-streams to handle the flow of request-response processing in a stream. While this may not appear to be a huge difference, it gave a huge boost to the server concurrency.

Disclaimer: With changes introduced in Akka HTTP module from Spray framework, Akka HTTP has become quite powerful in terms of throughput and low-level concurrency control. While this is a huge boost, Akka HTTP is not for everyone and every project. I would like to compare Akka HTTP with Java's equivalent of Netty. It is extremely powerful for creating any sort of distributed computing client/server application over HTTP but adds a lot of overhead for simple web-based applications. Akka HTTP is not a web framework but a suite of libraries to provide low-level controls and API for HTTP communication. One should explore Lagom for reactive micro-services and Play Framework for web-based applications as they are shipped with some of those production battles tested patterns, a framework, and other ecosystem support.


Application

In this article, we are going to create RESTful apis including a POST and a GET APIs to serve Employee records from a database. There are 2 operations to going to handle in this application.

  • Get Employee by ID
  • Query Employee by ID, FirstName, LastName

Complete application source can be found here.

For the sake of simplicity, we are not going to fetch the data from a real database, but from an in-memory collection. However, we are going to synthesize an artificial delay in the query from the in-memory collection to make it appear coming from a backend store.

We start by creating case classes for Employee and Address records to hold and server the Employee records.

  case class Employee(id: String,
                      firstName: String,
                      lastName: String,
                      active: Boolean,
                      address: Address)

  case class Address(line1: String,
                     line2: String,
                     city: String,
                     state: String,
                     zipCode: String)

Employee Repository

In order to serve data and synthesize a database request, we are going to create a repository that can fetch the data from the database. However, for the sake of simplicity, we will keep the data in memory and create data access methods with an artificial delay to synthesize an actual transaction.

  val EmployeeDB: Seq[Employee] = ...
  import akka.pattern.after
  def fetchDBWithDelay(): Future[Seq[Employee]] = {
    val randomDuration = (Math.random() * 5 + 3).toInt.seconds
    after(randomDuration, scheduler) {
      Future {
        EmployeeDB
      }
    }
  }

In the above code snippet, we created a method that returns an in-memory collection of Employee records after a simulated delay. Please note that we are not blocking the thread with a delay, rather creating a future scheduler that completes the future after a given time duration. It is extremely vital to understand, that the Akka HTTP uses Actors and Streams for dispatching and handling the HTTP requests, any blocking requests in the HTTP handler will negatively degrade the concurrency and performance of the server endpoint. We should ideally handle the HTTP requests in a non-blocking reactive fashion. In this article, we are going to use Future's to handle the requests.

We define a method for fetching the employee records for a given employee id. It uses the in-memory employee records collection and returns the data. We additionally define a couple of exceptions in case of dubious records found or no records found. We will later use these exceptions to send custom replies from the REST API.

def getEmployeeById(id: String) = fetchDBWithDelay().map { db =>
    val data = db.filter(_.id == id)
    if (data.isEmpty)
      throw new EmployeeNotFoundException
    else if (data.length > 1)
      throw new DubiousEmployeeRecordsException
    else
      data(0)
  }

Additionally, we also define a method to query employee records for a given search criteria.

def queryEmployee(id: String, firstName: String, lastName: String): Future[Seq[Employee]] = {
    fetchDBWithDelay().map { db =>
      db.filter { elem =>
        elem.id == id && elem.firstName == firstName && elem.lastName == lastName
      }
    }
  }

Following is the gist for the entire Employee Repository. We have created a seperate actor system for synthesizing the concurrenct non-blocking transactions in the repository. However, creating a seperate actory system for this is not the best use of system resources in all use cases.

Adding Akka HTTP

Entire Akaa suite is composed into a set of modules which needs to be imported independently to support a certain functionality. Here, we need to import Akka HTTP module for creating the server and defining REST endpoints. For the purpose of marshaling and unmarshalling, we plan to use spray-json which also has an inbuilt support with Akka HTTP.

We can use following SBT snippet for importing Akka HTTP and supporting modules including SLF4J logging adapter for Akka, Akka-Streams, Akka-Testkit etc.

      "com.typesafe.akka" %% "akka-http" % akkaHttpVersion,
      "com.typesafe.akka" %% "akka-slf4j" % akkaVersion,
      "ch.qos.logback" % "logback-classic" % "1.0.0" % "runtime",
      "com.typesafe.akka" %% "akka-http-spray-json" % akkaHttpVersion,
      "com.typesafe.akka" %% "akka-http-xml" % akkaHttpVersion,
      "com.typesafe.akka" %% "akka-stream" % akkaVersion,
      "com.typesafe.akka" %% "akka-http-testkit" % akkaHttpVersion % Test,
      "com.typesafe.akka" %% "akka-testkit" % akkaVersion % Test,
      "com.typesafe.akka" %% "akka-stream-testkit" % akkaVersion % Test,
      "org.scalatest" %% "scalatest" % "3.0.1" % Test

The above imports should be able to pull in all the required depedencies to create low-level server APIs and support of various protocols.

Setting up Akka HTTP

We start with creating an ActorSystem that will be used by the Akka HTTP to handle and dispatch concurrent connection requests.

  implicit val actorSystem = ActorSystem("AkkaHTTPExampleServices")

Additionally, for maximizing the performance of the application, Akka HTTP uses streams for handling the request/response. In Akka Stream's convention, we need to specify the flow materializer which specifies how the request/response flow will get processed. Akka HTTP recommends Actors for handling the incoming request and ships with an Actor Materializer to magically handle the flow for us.

  implicit val materializer = ActorMaterializer()

Akka HTTP provides high-level routing APIs via a DSL to create and define the HTTP routes and how they should be handled. Each route is composed of one or more Directives in a nested fashion that defines how the request and response should be handled.

This model of defining the routes looks entirely different from conventional J2EE based Web/HTTP frameworks but provides a great level of flexibility. As an example one can start with a route stating a path fragment /api, add GET/POST directives, add caching routes, add exception handlers, add future processing handler and add final response handler complete routes to send back the HTTP response as a serialized objects. A very simple route can be as simple as following

lazy val apiRoutes: Route = pathPrefix("api") {
    get {
      complete {
        "Hello world"
      }
    }
}

Finally, we configure the Akka HTTP to listen to a port and start serving the routes. Here's a snippet for the complete definition of Akka HTTP route processing.

object Server extends App {

  implicit val actorSystem = ActorSystem("AkkaHTTPExampleServices")
  implicit val materializer = ActorMaterializer()

  lazy val apiRoutes: Route = pathPrefix("api") {
    get {
      complete {
        "Hello world"
      }
    }
  }

  Http().bindAndHandle(apiRoutes, "localhost", 8080)
  logger.info("Starting the HTTP server at 8080")
  Await.result(actorSystem.whenTerminated, Duration.Inf)
}

With this, the Akka HTTP server should start listening to port 8080 and should be processing a request with 200 OK at http://localhost:8080/api

Completing the application

In this application, we planned to create 2 routes for querying the employee records. We define a RESTful way of accessing the employee record by id and another to POST a search query using detailed demographic information.

  • /api/employee/{id} GET route with path param id.
  • /api/employee/query POST route with JSON payload of FirstName, LastName, ID.

Here's the snippet of the complete route request handlers.

Akka high-level routing DSL API ships with a large number of pre-defined Directives that pretty much cover all of the generally used patterns of defining API's and handling request-response flow.

For the API's described above, we start with defining the route using a directive pathPrefix("api") to indicate that the route is supposed to capture anything starting with api. We narrow down the route to add a new directive pathPrefix("employee") to indicate any path with /api/employee. Since we need 2 REST endpoints above, we further add directives for the GET and POST requested concatenated to handle both GET and POST request for the above path fragment.

For the GET request above, we expect to get a path parameter for the Employee ID. To handle that, we use the directive path(Segment) to extract path parameters. For the POST request, we can use entity(as[T]) directive to extract the body payload stream as an instance of T.

Since we ought to use non-blocking flow channel to avoid blocking requests and limiting concurrency, we can use directive onComplete to pipe the flow handler on future completion.

Finally, we complete the request-response flow by using the complete directive and responding with an HTTP status code and the response body.

Marshalling/Unmarshalling support

In the application above, you might have noticed that we expect the request payload as JSON and the response payload as JSON as well. Akka HTTP ships with marshallers and un-marshallers for basic types like String, Numeric etc, but can also easily be extended with complex protocols. Luckily for us, Spray JSON has a module for Akka HTTP. We just to need to bring the required dependencies for akka-http-spray-json and define the implicit JSON formats for the request/response case classes. The following snippet does that for us.

object EmployeeJsonProtocol extends SprayJsonSupport with DefaultJsonProtocol {

    implicit val addressFormat = jsonFormat5(Address.apply)
    implicit val employeeFormat = jsonFormat5(Employee.apply)
    implicit val employeeQueryFormat = jsonFormat3(QueryEmployee.apply)
}
import EmployeeRepository._ 

Testing the REST Endpoints

This is all. Following are some snippets of the test results. The GET query for the employee ID where an Employee record exists with the id 101. We respond back with the Employee record as JSON with a status code of 200.

AkkaHTTPEmployeeGetQuerySuccess-1

The GET query for the employee id where dubious records exist and we cannot find a unique result. We respond back with a 404 and a custom error message.

AkkaHTTPEmployeeGetQueryFailure

The GET query for the employee id where no record exists in the database. We respond back with a 404 with a custom error message.

AkkaHTTPEmployeeGetQueryFailure-1

The POST query to search the database for the Employee records that match the search criteria. We respond back with an array of Employee records with a status code of 200.

AkkaHTTPEmployeePOSTQuerySuccess

Summary

Akka HTTP provides concise and neat API's and DSL to create RESTful applications. Akka HTTP is perfect for use to create integration layers for B2B communications while other frameworks like Lagom or Play should be preferred for complete web applications for support like templates, static assets, CSS pre-processing etc. Complete code for this REST application can be found here. Akka HTTP provides toolkit libraries with the support of high-level and low-level API's to create HTTP application. In this article, we are going to create RESTful application using Akka HTTP and Spray JSON. Also, don't forget to check out the cool documentation and other fantastic routing DSL.

Love Hasija

Love Hasija

Full Stack Research Engineer, Software Architect | Helped build next generation software systems | Distributed Systems Fanatic | Open Source Hacker.

Read More
Building REST APIs with Akka HTTP and Spray JSON
Share this