ScalaのSpray-routingとSlickで簡易RESTful APIつくってみる

What Spray-routing?

spray | Documentation » 1.2.4 / 1.3.4 » spray-routing
Spray-routingは、RESTfulなWebサービスのためのハイレベルかつ、柔軟なルーティングを提供するモジュール。簡単にルーティングを作れる。


What Slick?

tabakazu.hatenablog.com



準備

今回もPostgreSQLを使います。
以前のSlick記事にMacOSへのインストール方法書いてます。
作成するユーザ、データベース、テーブルも以前の記事に合わせます。

# PostgreSQL 起動
brew services start postgresql

# PostgreSQL 接続
psql -d postgres

## 今回の接続ユーザであるrootを作成
create user root;

## 今回の接続データベースであるrootを作成
create database root;

## PostgreSQL 接続終了
\q

# rootデータベースにrootユーザで接続
psql -d root -U root

## 今回使用するテーブル作成
create table players ( 
    id serial primary key,
    name varchar(80),
    birthday varchar(80)
);

## rootでの接続終了
\q



Installation

Spray-jsonも入れて、出力はJSONにします。

// build.sbt

name := "sprayrouting_app"
 
version := "1.0"
 
scalaVersion := "2.11.8"

libraryDependencies ++= Seq(
    "com.typesafe.akka" %% "akka-actor" % "2.4.10",
    "com.typesafe.slick" %% "slick" % "3.1.1",
    "org.postgresql" % "postgresql" % "9.4-1200-jdbc41",
    "io.spray" %% "spray-can" % "1.3.3",
    "io.spray" %% "spray-routing" % "1.3.3",
    "io.spray" %% "spray-json" % "1.3.2"
)


設定ファイルの作成

// src/main/resources/application.conf

pg_db = {
  url = "jdbc:postgresql://localhost/root?user=root"
  driver = org.postgresql.Driver
  connectionPool = disabled
  keepAliveConnection = true
}



Source code

// src/main/scala/Boot.scala

import akka.actor.{Props, ActorSystem}
import akka.io.IO
import spray.can.Http

object Boot extends App {
  implicit val system = ActorSystem("on-spray-can")
  val service = system.actorOf(Props[MyServiceActor])

  IO(Http) ! Http.Bind(service, interface = "localhost", port = 8080)
}

Boot.scalaはアクターのエントリーポイントを作成。

// src/main/scala/MyService.scala

import akka.actor.Actor
import scala.concurrent.Await
import scala.concurrent.duration.Duration
import scala.concurrent.ExecutionContext.Implicits.global
import slick.driver.PostgresDriver.api._
import spray.routing._
import spray.json._
import DefaultJsonProtocol._

class MyServiceActor extends Actor with MyService {
  def actorRefFactory = context
  def receive = runRoute(myRoute)
}

trait MyService extends HttpService {

  val db = Database.forConfig("pg_db")
  val players = TableQuery[Players]

  def getPlayers() = {
    val query = db.run(players.result)
    val result = Await.result(query, Duration.Inf)
    result.toJson.toString
  }

  def createUser(name: String, birthday: String) = {
    val query = db.run(players.map(p => (p.name, p.birthday)) += (name, birthday))
    val message = "created name:" + name + " player"
    message.toString
  }

  def getPlayer(id: Int) = {
    val query = db.run(players.filter { _.id === id }.result)
    val result = Await.result(query, Duration.Inf)
    result.toJson.toString
  }

  def updateUser(id: Int, name: String, birthday: String) = {
    val query = db.run(players.filter(_.id === id).map(p => (p.name, p.birthday)).update((name, birthday)))
    val message = "updated id:" + id + " player"
    message.toString
  }

  def deleteUser(id: Int) = {
    val query = db.run(players.filter { _.id === id }.delete)
    val message = "deleted id:" + id + " player"
    message.toString
  }

  val myRoute =
    path("players") {
      get {
        complete {
          getPlayers()
        }
      } ~
      post {
        formFields('name, 'birthday) { (name, birthday) =>
          complete {
            createUser(name, birthday)
          }
        }
      }
    } ~
    path("players" / IntNumber) { id =>
      get {
        complete {
          getPlayer(id)
        }
      } ~
      put {
        formFields('name, 'birthday) { (name, birthday) =>
          complete {
            updateUser(id, name, birthday)
          }
        }
      } ~
      delete {
        complete {
          deleteUser(id)
        }
      }
    }
}

MyService.scalaはルーティングとデータベース処理。
Awaitつかってブロッキングです。

// src/main/scala/Player.scala

import scala.concurrent.ExecutionContext.Implicits.global
import slick.driver.PostgresDriver.api._

class Players(tag: Tag) extends Table[(Int, String, String)](tag, "players") {
  def id = column[Int]("id", O.PrimaryKey)
  def name = column[String]("name")
  def birthday = column[String]("birthday")
  def * = (id, name, birthday)
}

Players.scalaには作成したplayersテーブルの情報。



Compile & Run

# コンパイル、実行
sbt compile run



Try Request

CRUD機能が使えるか、curl使って試します。

# curl インストール
brew install curl

# Try Create
curl -X POST -F "name=a" -F "birthday=1990-01-01" http://localhost:8080/players

# Try Read
curl -X GET http://localhost:8080/players
curl -X GET http://localhost:8080/players/1

# Try Update
curl -X PUT -F "name=b" -F "birthday=1980-12-30" http://localhost:8080/players/1

# Try Delete
curl -X DELETE http://localhost:8080/players/1