Sunday, April 26, 2015

Creating a REST API with Scala and Spray

In this second article in our Scala series, I'll be introducing you to the Spray framework, which will allow you to build a REST API. If you aren't already familiar with Spray, it's essentially an HTTP stack for Akka and Scala applications. It's embeddable, rather than a standalone server, and fairly easy to tie in. Spray is focused on HTTP integration layers, rather than being a full fledged web application (see Play), allowing you to create bridges between your service and other servers, clients and third party APIs like Twitter, etc., through HTTP. Just define your REST API and you're good to go.

Prerequisites: You'll want to install Scala and SBT. It also helps to be familiar with Akka Actors. This guide was written for Mac OS X users.

Spray vs. Existing JVM Solutions

So why bother building an HTTP stack from the ground up in Scala when you can already leverage Netty, Restlet, and Servlet containers? Well, with Spray you can stay true to Scala methodologies. Keep in mind that with the JVM alternatives, you end up with a lot of overhead. It could mean XML-based configuration when you'd rather be leveraging JSON. You might also be dealing with mutable data models that go against Scala programming models. Lastly, you're hooking into legacy APIs and adapter layers that end up limiting you in the long run.

As a Scala developer, you want the following, which Spray and Akka will allow:

  • Actor-based APIs that emphasize message protocols over method calling
  • Scala / Akka Futures for use with concurrent operations
  • A Case class based model for useful pattern matching and other benefits
  • Scala Collections which are richer than Java Collections
  • Unified thread-pool management which is impossible with an adapter layer like Netty

Our Application

Now, rather than build an application from the ground up, you'll be cloning an existing, working application, and I'll just be explaining all the different components.

git clone https://github.com/roblayton/scala-spray-rest.git

cd scala-spray-rest
sbt run

The above command will start our application on localhost:8080 but you can change the host and port in the application.conf file:

service {
  host = "localhost"
  port = 8080
}

These values are made accessible to our application through Configuration.scala, where you can also set defaults.

package com.roblayton.example.config

import com.typesafe.config.ConfigFactory
import util.Try

trait Configuration {
  val config = ConfigFactory.load()

  lazy val serviceHost = Try(config.getString("service.host")).getOrElse("localhost")
  lazy val servicePort = Try(config.getInt("service.port")).getOrElse(8080)
}

Our Endpoints

  • GET /hello Return "Hello World!"
  • GET /fragments Return a list of Fragments
  • GET /fragment/<id> Return a specific Fragment
  • POST /fragment Create a Fragment

These endpoints are explicitly defined in Main.scala through the following directives. Note that we aren't modifying an sort of truly persistent data structure, but an in-memory list. We'll talk more about that in the next section. If you'd like you're data to persist in a MySQL database, please skip ahead to this guide.

startServer(interface = serviceHost, port = servicePort) {
  get {
    path("fragments") {
      complete {
        fragments
      }
    }
  } ~
  get {
    path("fragment" / IntNumber) { index =>
      complete {
        fragments(index)
      }
    }
  } ~
  post {
    path("fragment") {
      entity(as[JObject]) { fragmentObj =>
        val fragment = fragmentObj.extract[MineralFragment]
        fragments = fragment :: fragments
        complete {
          "OK"
        }
      }
    }
  }
}

Our In-Memory List

We initialize our list with 3 prepopulated elements, and through our endpoints, we're able to access and add to this list.

package com.roblayton.spray

import org.json4s._
import org.json4s.native.Serialization

trait Fragment

case class MineralFragment(name: String, kind: String, weight: Double) extends Fragment

object Fragment {
  var fragments = List[Fragment](
    MineralFragment("amorphous", "Graphite", 0.01),
    MineralFragment("flake", "Graphite", 0.3),
    MineralFragment("vein", "Graphite", 0.55))

  private implicit val formats = Serialization.formats(ShortTypeHints(List(classOf[Fragment])))
}

Running Our Application

Now, let's run our application and send some requests.

# run this command from the root directory
sbt run

# open up a new shell window
# hello world
curl -i http://localhost:8080/hello

# request fragments
curl -i http://localhost:8080/fragments

# insert a new fragment
curl -i -H "Content-Type: application/json" -X POST -d '{"name":"Name", "kind": "Kind", "weight":0.06}' http://localhost:8080/fragment

# request fragments again to see what you just added
curl -i -X http://localhost:8080/fragments

No comments:

Post a Comment