Typesafe Activator

Config, Guice, Akka and Spray

Config, Guice, Akka and Spray

Eric Halpern
Source
December 1, 2014
scala spray akka json4s scalatest guice depedency-injection scaladays2014

A modular scala framework for building and testing real spray REST services

How to get "Config, Guice, Akka and Spray" on your computer

There are several ways to get this template.

Option 1: Choose config-guice-akka-spray in the Typesafe Activator UI.

Already have Typesafe Activator (get it here)? Launch the UI then search for config-guice-akka-spray in the list of templates.

Option 2: Download the config-guice-akka-spray project as a zip archive

If you haven't installed Activator, you can get the code by downloading the template bundle for config-guice-akka-spray.

  1. Download the Template Bundle for "Config, Guice, Akka and Spray"
  2. Extract the downloaded zip file to your system
  3. The bundle includes a small bootstrap script that can start Activator. To start Typesafe Activator's UI:

    In your File Explorer, navigate into the directory that the template was extracted to, right-click on the file named "activator.bat", then select "Open", and if prompted with a warning, click to continue:

    Or from a command line:

     C:\Users\typesafe\config-guice-akka-spray> activator ui 
    This will start Typesafe Activator and open this template in your browser.

Option 3: Create a config-guice-akka-spray project from the command line

If you have Typesafe Activator, use its command line mode to create a new project from this template. Type activator new PROJECTNAME config-guice-akka-spray on the command line.

Option 4: View the template source

The creator of this template maintains it at https://github.com/ehalpern/sandbox#master.

Option 5: Preview the tutorial below

We've included the text of this template's tutorial below, but it may work better if you view it inside Activator on your computer. Activator tutorials are often designed to be interactive.

Preview the tutorial

Config, Guice, Akka and Spray

Presents a light framework for building reactive REST services in a modular, testable fashion using Typesafe Config, Guice, Akka and Spray.

This tutorial will demonstrate:

  • How to use dependency injection to simplify and modularize code (and tame implicits)
  • How to inject configuration properties to avoid boilerplate configuration code
  • Clear, concise patterns for implementing reactive, end-to-end REST services using Spray
  • How to combine these techniques to provide a simple, complete, scalable structure for building REST server applications

The Example - A World Climate API

We'll explore a simple but complete example - A REST API that provides model-based climate data by country.

Here are the key components:

Dependency Injection

Dependency injection (DI) provides a concise way to wire applications together. It removes the need for new and factories (both of which tend to complicate code and interfere with modularity).

When using a DI system, each implementation class can simply declare its dependencies in its constructor and rely on the system to provide them on instantiation.

The ClimateServiceImpl class provides a simple example. It declares two dependencies:

The DI system will provide both of these automatically when it instantiates the service.

Noticed the ugly @Inject() annotation? Guice (and JSR 330) requires this to mark all constructors that are candidates for injection.

Configuration Injection

An implementation may also require configuration, and we can use DI for this too.

WbClimateClientImpl provides an example. It declares an endpoint constructor parameter

@Named("wbclient.endpoint") endpoint: String
used to access the World Bank API.

This annotation tells the DI system that the value should be read from the "wbclient.endpoint" configuration property (defined in application.conf)

The @Named annotation is a Guice/JSR330 way to qualify a dependency that cannot be identified by its type alone.

Behind the scene

During DI system initialization, we load a special ConfigModule that reads the typesafe configuration and binds each property value to a @Named("[config-key]") annotation. This makes all config properties available for injection.

Modules and Bindings

The complete application is composed of a set of modules. Each module defines a logical subsystem and contains all necessary implementation bindings.

The program entry point Main wires the application by installing MainModule. This module, in turn, installs all of the sub-modules required to run the server:

  • ConfigModule loads the Typesafe configuration and creates a @Named binding for every configuration property
  • AkkaModule initializes the Akka system and wires Akka to use Guice for actor instantiation. This module also binds the AkkaRefFactory and ExecutionContext so they're available for injection to all services that use Akka and Futures.
  • ServicesModule binds all application services
  • SprayModule enables an Http Server and creates the actor to handle requests
  • ApisModule registers all REST APIs

Implementing the REST API

ClimateApi implements our example REST API. It defines GET climate which retrieves climate data by region and period. Here's an example:


curl 'localhost:9080/climate?location=FJI&fromYear=1980&toYear=1999'
# returns
{"location": "FJI" "fromYear": 1980, "toYear": 1999,
  "precipitation": { "annual": 2166.58, "unit": "Centimeters" },
  "temperature":   { "annual": 24.78, "unit": "Celsius" }
}

The implementation uses the Spray Routing DSL to declare the resource, method and parameters, and delegates the real work to ClimateService in the complete block:


complete {
  climateService.query(location, from, to)
}
The query call returns a Future[ClimateStats]. The complete directive, in effect, defines a completion handler for this future. When the ClimateStats result is available, it's marshalled to JSON and sent back in a response.

So though the code reads sequentially, it is actually executed in an asynchronous pipeline.

Implementing ClimateService

ClimateServiceImpl implements the ClimateService which provides a single query method that returns climate data by location and time.

The query implementation must make two remote requests to the World Bank Climate API (using WbClimateClient) to obtain precipitation data and temperature data. It then combines this data in a single ClimateStats result.

As with the API implementation, it appears sequential but is actually asynchronous.


(for {
  temp <- wbClient.fetchTemperatureStats(location, from, to)
  rain <- wbClient.fetchPrecipitationStats(location, from, to)
} yield {
  ClimateStats.fromData(location, from, to, temp, rain)
})
The wbClient calls both return Future[WbClimateData]. The for comprehension combines these to create a Future[ClimateStats] result.

Take a look at Funcitonal Futures to learn more about how map and for comprehensions can be used to combine Futures.

Implementing an Async API Client

WbClimateClientImpl implements the async http client for the World Bank Climate API using:

Let's take a look at fetchTemperatureStats to see how things fit together.

This method builds the request and than calls pipeline(Get(uri)) to push it into an asynchronous processing pipeline.

The pipeline defines the stages for http processing:


val pipeline: HttpRequest => Future[Seq[WbClimateData]] =
  sendReceive ~>
    unmarshal[Seq[WbClimateData]]

The sendReceive stage sends the request (using Akka IO) and processes the response on completion.

The unmarshal stage deserializes the response into Seq[WbClimateData] when it's ready. We enable Json4s to handle unmashalling by mixing in WbClimateData.JsonProtocol.

The pipeline returns the future result for the request.

Unit Testing the API

ClimateApiSpec defines the unit test for ClimateApi.

It uses the spray testkit and the Mokito mocking framework to run an isolated test against ClimateApi. Rather than use dependency injection in this case, we instantiate the ClimateApi directly and pass in a mock service for it to run against.

This test is structured so we can reuse it for running an integration test which we'll explore in next.

Integration Testing

ClimateApiIntegSpec extends the unit test to run against a real service.

Instead of creating route with ClimateApi and a mock ClientService, it uses the getAppRouteInstance defined in IntegTestSupport

This returns a route that is implemented as the real application, which has been constructed using the DI framework.

You can run this test using "sbt integ:test"

comments powered by Disqus