[Akka HTTP + Slick]誰でも手軽に作れるScala API

#はじめに

外資系で働くプログラマーの@yukihirai0505です。
ポートフォリオは[こちら](https://yukihirai0505.github.io/portfolio/)。

Scalaってちょっととっつきにくいな。。。と思ってる方のためにScalaで簡単にAPIが作れる[Akka HTTP](http://doc.akka.io/docs/akka/2.4.10/scala/http/)のチュートリアルを書いてみます。

***この記事のゴール***
=> ***"ゼロベースで[Akka HTTP](http://doc.akka.io/docs/akka/2.4.10/scala/http/)と[Slick](http://slick.lightbend.com/)を使用したAPIを作成できる"***

***開発環境について***

`OS X El Capitan 10.11.6`

今回はMacにそのまま環境を構築していきます。
できるだけシンプルにするため必要最低限のものだけ取り扱うようにしま
す。

- [Java 1.8](https://java.com/en/)
- [Scala 2.11.8](http://www.scala-lang.org/)
- [sbt 0.13.8](http://www.scala-sbt.org/)
- [Slick 3.1.1](http://slick.lightbend.com/)
- [Akka  2.4.10](http://doc.akka.io/docs/akka/2.4.10/scala/http/)
- [MySQL 5.6.23](https://www.mysql.com/)

最終的にはこちらの記事を参考に下記のようなAPIを作成します。
=> [RESTful API Design](http://www.slideshare.net/apigee/restful-api-design-second-edition)

```
GET    /dogs     list dogs
POST   /dogs     create a new dog
GET    /dogs/:id show the dog
PUT    /dogs/:id update the dog
DELETE /dogs/:id delete the dog
```

また今回こちらの記事で作成するAPIのサンプルは
GitHubからもご覧いただけます。
=> [サンプルソース](https://github.com/yukihirai0505/dog-api)

# 事前知識

## Scalaについて

Scalaの基本的な文法や使用方法については
有料ですが下記のドットインストールのレッスンがオススメです。
=> [Scala入門](http://dotinstall.com/lessons/basic_scala)

お金かけたくない!という方は英語ですがこちらのチュートリアルがオススメです。
=> [Scala Documentation](https://docs.scala-lang.org/tour/tour-of-scala.html)

また下記の記事ではScala入門時に役立つ情報がまとめられてます。
=> [Scala入門時に役立つ情報まとめ](http://qiita.com/nesheep5/items/49019b9df4836d36ec1f)

また、ScalaはJVM上で動くので
Javaのインストールが必須になります。
あとで簡単にインストール手順について記述しておきます。

## Akka HTTPについて

> The Akka HTTP modules implement a full server- and client-side HTTP stack on top of akka-actor and akka-stream. It's not a web-framework but rather a more general toolkit for providing and consuming HTTP-based services. While interaction with a browser is of course also in scope it is not the primary focus of Akka HTTP.

簡単にいうとWEBフレームワークではなく、HTTPモジュールです。
異なるレベルでのAPIを提供しているので作成したいアプリケーションに適したものを自分で選択することができます。フレキシブルですね。

それぞれのAPIについては下記リンクをご覧下さい。
=> [The modules that make up Akka HTTP](http://doc.akka.io/docs/akka/2.4.10/scala/http/introduction.html#The_modules_that_make_up_Akka_HTTP)

例えば、低レベルなものだとリクエストやレスポンスのカスタマイズがフレキシブルに行えたりします。
=> [Low-level HTTP server APIs](http://doc.akka.io/docs/akka/2.4.10/scala/http/introduction.html#Low-level_HTTP_server_APIs)

また度重なるリリースによりパフォーマンスもだいぶ向上していて2.4.9のリリースでは

> Performance is on-par or better than Spray.

とも言われています。

詳細はこちらをご覧下さい。
=> [Akka 2.4.9 Released](http://akka.io/news/2016/08/19/akka-2.4.9-released.html)

また、sprayについては少し古いですがこちらのベンチマークが参考になります。
=> [Benchmarking spray](http://spray.io/blog/2013-05-24-benchmarking-spray/)

## Slickについて

> Slick (“Scala Language-Integrated Connection Kit”) is Typesafe‘s Functional Relational Mapping (FRM) library for Scala that makes it easy to work with relational databases.

ScalaのFRM (Functional Relational Mapping)ライブラリです。
Scalaのコレクションを扱うかのような操作でDBにアクセスしてデータを操作できるので、使い勝手が良いです。

また

> Plain SQL Support

ともあるように直接SQLを実行することもできます。
今回はMySQLと連携させるための設定を行います。
詳しくはこちらをご覧下さい。
=> [What is Slick?](http://slick.lightbend.com/doc/3.1.1/introduction.html)

# 環境構築

## Javaのインストール

まずは、Javaのインストールをしましょう。
今回はJavaのバージョン1.8を使用していきます。
ご自身の環境にJavaが入っている場合は次の[sbtのインストール](http://qiita.com/yukihirai0505/private/b9bf6a2810abc2f7a875#sbt%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB)を行ってください。
入っているかどうかは、コマンドラインで
`java -version`
を実行することで確認できます。
`java version "1.8.~~"`
みたいな表示がでれば1.8が入ってます。
入っていない場合、
Macの方は `brew cask` でダウンロードがお手軽です。
まずはHomebrewのインストール

```
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
brew update
```

次に `brew cask` のインストール

```
brew install caskroom/cask/brew-cask
brew cask install java
```

これでjava8がインストールできます。(2016.9.16時点)

Mac以外の方やbrewで入れたくないなあという方はOracleのページからインストールしましょう。

- [Mac OS X に Oracle Java 8 (JDK) をインストールする手順](http://weblabo.oscasierra.net/java-install-oracle-jdk8-macosx/)
- [Java
初心者でも簡単にできる！Javaをインストールする方法(Windows編)](http://techacademy.jp/magazine/8998)


## sbtのインストール

> The interactive build tool

sbtはビルドツールです。

Scalaを使用するために下記のリンクの内容に従って
sbtをインストールします。

[Installing sbt on Mac](http://www.scala-sbt.org/release/docs/Installing-sbt-on-Mac.html)

Mac以外の方はMacを購入するもしくは下記のリンクを参考にインストールして下さい。

[Installing sbt](http://www.scala-sbt.org/release/docs/Setup.html)

Macの方はHomebrewが入ってれば

`brew install sbt`

で sbt をインストールできます。

ちなみに Java が入っていない場合だと

```
sbt: Java 1.6+ is required to install this formula.JavaRequirement unsatisfied!
```

みたいなエラーがでます。

## MySQLのインストール

今回はSlickをMySQLと連携させるため、

`brew install mysql`

でmysqlを入れておきましょう。

すでにMySQLが入っていればいいですが
入っていない場合はユーザーの設定などしておきましょう。
5.7以上のバージョンが入っている場合はこちらを参考に設定してみてください。
=> [MySQL5.7の初期設定まとめ](http://qiita.com/ritukiii/items/08df5be6d50871124aaf)

自分のリポジトリでは `docker-compose up` すれば
MySQL5.7のDBをDockerで立ち上げるようにしてます。

=> [docker-compose.yml](https://github.com/yukihirai0505/dog-api/blob/master/docker-compose.yml)

# APIの実装

## プロジェクトの作成

次にお好きな場所にディレクトリを作成します。

```
mkdir [プロジェクト名]
```

作成したディレクトリに移動して
`build.sbt` ファイルを作成します。

```scala:build.sbt
scalaVersion := "2.11.8"
```

これでコマンドラインで

`sbt console` コマンドを入力して実行すれば `Scala2.11.8` を使用できることがわかります。

ちょっとだけScalaを触ってみましょう。

今回は下記をimportしてみて使ってみましょう。

http://www.scala-lang.org/api/2.11.8/index.html#scala.concurrent.duration.package

```
sbt console
```

ScalaのREPLが起動してScalaをインタラクティブに試すことができます。
ちなみにScalaのREPLは `control+c` もしくは `:q`で終了することができます。


```
import scala.concurrent.duration._
```

を実行して

```
1.day.toSeconds.toInt
```

を実行してみましょう。

下記のような感じになれば大丈夫です。

```
sbt console
Picked up _JAVA_OPTIONS: -Dfile.encoding=UTF-8
...
[info] Done updating.
[info] Starting scala interpreter...
[info]
Welcome to Scala 2.11.8 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_77).
Type in expressions for evaluation. Or try :help.
scala> import scala.concurrent.duration._
import scala.concurrent.duration._
scala> 1.day.toSeconds.toInt
res0: Int = 86400
```

ちょっとだけScala触ってる感を感じることが出来たかと思います。
ちなみにやってることは
[scala.concurrent.duration.package](http://www.scala-lang.org/api/2.11.8/index.html#scala.concurrent.duration.package)をimportすることで
Intの後にday,hourなどの期間を表す単語をつけるとその期間を表すオブジェクトにできます。

今回は `1.day` つまり 1日を `toSeconds` で 秒に変換しています。
そのままではLong型なので `toInt` で Int型に変換しています。

では、Scalaを感じることができたところで
今回作成するAPIのディレクトリ構成を用意していきます。
下記のような構成で進めるため必要なディレクトリやファイルを作成してください。

```
[プロジェクトディレクトリ]
└── src
    └── main
        ├── resources
        │   └── application.conf
        └── scala
            ├── Server.scala
            ├── connection
            │   └── MySQLDBImpl.scala
            ├── dao
            │   └── DogDao.scala
            ├── http
            │   └── DogRoutes.scala
            └── model
                └── Dog.scala
```

## Akka HTTPの設定

次にAkka HTTPを使用するための設定をしていきます。

先ほど作成した `build.sbt` ファイルを編集します。

```scala:build.sbt
scalaVersion := "2.11.8"

libraryDependencies ++= {
  val akkaV = "2.4.10"
  Seq(
    "com.typesafe.akka" %% "akka-http-experimental" % akkaV
  )
}
```

Akka HTTPを試してみるために
`src/main/scala` 配下に
`Sample.scala` ファイルを作成します。

```scala:Sample.scala
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.http.scaladsl.server.Directives._
import akka.stream.ActorMaterializer
import scala.io.StdIn

object Sample {
	def main(args: Array[String]) {

		implicit val system = ActorSystem("my-system")
		implicit val materializer = ActorMaterializer()
		// needed for the future flatMap/onComplete in the end
		implicit val executionContext = system.dispatcher

		val route =
			path("hello") {
				get {
					complete(HttpEntity(ContentTypes.`text/html(UTF-8)`, "<h1>Say hello to akka-http</h1>"))
				}
			}

		val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)

		println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
		StdIn.readLine() // let it run until user presses return
		bindingFuture
			.flatMap(_.unbind()) // trigger unbinding from the port
			.onComplete(_ => system.terminate()) // and shutdown when done
	}
}
```

作成したらコマンドラインでプロジェクトのルートディレクトリで

`sbt run` を実行します。
まだ `sbt console` でScalaのREPLが起動している場合は `control+c` と入力して終了しておきましょう。

`sbt run` がうまく実行できれば
下記のようなログが見れます。
http://localhost:8080/hello にアクセスしてみましょう。

うまくいかない場合は `sbt run` を実行している場所がプロジェクトのルートディレクトリかどうかや
`src/main/scala` 配下に `Sample.scala` ファイルを作成しているかを確認してください。

```
sbt run                                                                                                              [master ~/cyberbuzz/test/akka_slick]
Picked up _JAVA_OPTIONS: -Dfile.encoding=UTF-8
...
[info] Running Sample
Server online at http://localhost:8080/
Press RETURN to stop...
```

ブラウザで `Say hello to akka-http` を確認できればAkka HTTPの設定は完了です。

`Sample.scala` ファイルはもう使用しないので削除しておきましょう。

またサーバーもコマンドラインで `return`キーを入力して止めておきましょう。


## Slickの設定

まずは今回接続するDBを作成しておきます。
MySQLで適当なデータベースを作成してください。
一応コマンドラインでのDB作成について記載しておきます。

```
mysql -u[ユーザー名] -p[パスワード]

CREATE DATABASE [適当なDB名];
```

次に `build.sbt` ファイルに設定を書いていきます。

```scala:build.sbt
scalaVersion := "2.11.8"

libraryDependencies ++= {
  val akkaV = "2.4.10"
	val slickVersion = "3.1.1"
  Seq(
    "com.typesafe.akka" %% "akka-http-experimental" % akkaV
		, "com.typesafe.slick" %% "slick" % slickVersion
		, "com.typesafe.slick" % "slick-hikaricp_2.11" % slickVersion
		, "mysql" % "mysql-connector-java" % "8.0.11"
		, "org.slf4j" % "slf4j-nop" % "1.6.4"
  )
}
```

次に `src/main/resources` に `application.conf` ファイルを作成します。


下記は適宜ご自身の環境に合わせて変更してください。

- [ユーザー名]
- [パスワード]
- [作成したDB名]

```application.conf
mysql = {
  dataSourceClass="com.mysql.cj.jdbc.MysqlDataSource"
  properties {
    user="[ユーザー名]"
    password="[パスワード]"
    databaseName="[作成したDB名]"
    serverName="localhost"
    portNumber="3306"
  }
  numThreads=10
}
```


次に `src/main/scala` 配下に `connection` ディレクトリを作成してDBの設定を書いていきます。

`MySQLDBImpl.scala` ファイルを作成します。

```scala:MySQLDBImpl.scala
package connection

import slick.driver.MySQLDriver

trait MySQLDBImpl {

	val driver = MySQLDriver

	import driver.api._

	val db: Database = MySqlDB.connectionPool

}

private[connection] object MySqlDB {

	import slick.driver.MySQLDriver.api._

	val connectionPool = Database.forConfig("mysql")

}
```

Slickの設定は以上です。

## テーブルの作成

次にテーブルを作成していきます。

まず `build.sbt` ファイルに `akka-http-spray-json-experimental` を追加します。

```scala:build.sbt
scalaVersion := "2.11.8"

libraryDependencies ++= {
  val akkaV = "2.4.10"
	val slickVersion = "3.1.1"
  Seq(
    "com.typesafe.akka" %% "akka-http-experimental" % akkaV
		, "com.typesafe.akka" %% "akka-http-spray-json-experimental" % akkaV
		, "com.typesafe.slick" %% "slick" % slickVersion
		, "com.typesafe.slick" % "slick-hikaricp_2.11" % slickVersion
		, "mysql" % "mysql-connector-java" % "5.1.38"
		, "org.slf4j" % "slf4j-nop" % "1.6.4"
  )
}
```

`src/main/scala/model` 配下に `Dog.scala` ファイルを作成します。

DogはIDと名前だけを持ったシンプルな作りにしておきます。

```scala:Dog.scala
package model

import connection. MySQLDBImpl
import spray.json.DefaultJsonProtocol

trait DogTable extends DefaultJsonProtocol {
	this: MySQLDBImpl =>
	
	import driver.api._

	implicit lazy val dogFormat = jsonFormat2(Dog)
	implicit lazy val dogListFormat = jsonFormat1(DogList)

	class DogTable(tag: Tag) extends Table[Dog](tag, "dog") {
		val id = column[Int]("id", O.PrimaryKey, O.AutoInc)
		val name = column[String]("name")

		def * = (name, id.?) <>(Dog.tupled, Dog.unapply)

	}

}

case class Dog(name: String, id: Option[Int] = None)
case class DogList(dogs: List[Dog])
```

*追記* `def * = (name, id.?) <>(Dog.tupled, Dog.unapply)` の役割について下記のリンクで詳しく説明してる方がいらっしゃったのでリンク紹介しておきます。

=> [scala slick method I can not understand so far](https://stackoverflow.com/questions/13906684/scala-slick-method-i-can-not-understand-so-far?answertab=votes#tab-top)

次にDAOを作成します。

`src/main/scala/dao` 配下に `DogDao.scala` ファイルを作成します。

```scala:DogDao.scala
package dao

import connection.MySQLDBImpl
import model.{Dog, DogTable}

import scala.concurrent.Future

trait DogDao extends DogTable with MySQLDBImpl {

	import driver.api._

	protected val dogTableQuery = TableQuery[DogTable]

	def create(dog: Dog): Future[Int] = db.run {
		dogTableAutoInc += dog
	}


	def update(dog: Dog): Future[Int] = db.run {
		dogTableQuery.filter(_.id === dog.id.get).update(dog)
	}


	def getById(id: Int): Future[Option[Dog]] = db.run {
		dogTableQuery.filter(_.id === id).result.headOption
	}


	def getAll: Future[List[Dog]] = db.run {
		dogTableQuery.to[List].result
	}


	def delete(id: Int): Future[Int] = db.run {
		dogTableQuery.filter(_.id === id).delete
	}

	def ddl = db.run {
		dogTableQuery.schema.create
	}

	protected def dogTableAutoInc = dogTableQuery returning dogTableQuery.map(_.id)

}
```

## ルートの作成

下記のAPIを作成します。

```
GET    /dogs     list dogs
POST   /dogs     create a new dog
GET    /dogs/:id show the dog
PUT    /dogs/:id update the dog
DELETE /dogs/:id delete the dog
```

`src/main/scala/http` 配下に `DogRoutes.scala` ファイルを作成します。

```scala:DogRoutes.scala
package http

import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import akka.http.scaladsl.model.HttpResponse
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.directives.MethodDirectives
import dao.DogDao
import model.Dog

import scala.concurrent.ExecutionContextExecutor

trait DogRoutes extends SprayJsonSupport {
	this: DogDao =>

	implicit val dispatcher: ExecutionContextExecutor

	val routes = pathPrefix("dogs") {
		pathEnd {
			get {
				complete(getAll)
			} ~ post {
				entity(as[Dog]) { dog =>
					complete {
						create(dog).map { result => HttpResponse(entity = "dog has been saved successfully") }
					}
				}
			}
		} ~
			path(IntNumber) { id =>
				get {
					complete(getById(id))
				} ~ put {
					entity(as[Dog]) { dog =>
						complete {
							val newDog = Dog(dog.name, Option(id))
							update(newDog).map { result => HttpResponse(entity = "dog has been updated successfully") }
						}
					}
				} ~ MethodDirectives.delete {
					complete {
						delete(id).map { result => HttpResponse(entity = "dog has been deleted successfully") }
					}
				}
			}
	}
}
```

少しだけ解説すると、

```
val routes = pathPrefix("dogs")
```

最初の部分で、 `pathPrefix` つまりURL部分で最初につける名前を決定します。

```
http://localhost:8080/dogs
```

といったところです。

次にその中で

`pathEnd` と `path(IntNumber)` がありますが

`pathEnd` 部分で下記のルートを指定

```
GET    /dogs     list dogs
POST   /dogs     create a new dog
```

`path(IntNumber)` 部分で下記のルートを指定しています。

```
GET    /dogs/:id show the dog
PUT    /dogs/:id update the dog
DELETE /dogs/:id delete the dog
```

非常にお手軽にルーティングできていることがわかります。

## サーバーの作成

最後にサーバーファイルを作成します。

`src/main/scala` 配下に `Server.scala` ファイルを作成します。
アプリを起動したときに、最初にテーブルにいくつかデータを入れるようにしておきます。

下記のサイトを参考にお犬さんの人気の名前をチョイスしてみました。
=> [TOP 100 MOST POPULAR MALE AND FEMALE DOG NAMES](http://dogtime.com/top-100-dog-names)

```scala:Server.scala
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.stream.ActorMaterializer
import dao.DogDao
import model.Dog
import http.DogRoutes
import scala.io.StdIn

import scala.concurrent.ExecutionContextExecutor

object Server extends App with DogRoutes with DogDao {

	implicit val system: ActorSystem = ActorSystem()

	implicit val materializer = ActorMaterializer()

	implicit val dispatcher: ExecutionContextExecutor = system.dispatcher

	ddl.onComplete {
		_ =>
			create(Dog("Bailey"))
			create(Dog("Max"))
			create(Dog("Charlie"))
			create(Dog("Bella"))
			create(Dog("Lucy"))
			create(Dog("Molly"))
			val bindingFuture = Http().bindAndHandle(routes, "localhost", 8080)
			println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
			StdIn.readLine()
			bindingFuture
				.flatMap(_.unbind())
				.onComplete(_ => system.terminate())
	}
}
```

プロジェクトのルートディレクトリで

`sbt run`

とすればアプリを起動することができます。

Mysqlを起動するのを忘れないようにしましょう。
Homebrewで入れた方は下記のコマンドで起動しておきましょう。

`mysql.server start`

# curlでテスト

サーバーが立ち上がったら下記のコマンドでそれぞれ実行して試してみることができます。
よかったら使用してみてください。

- list dogs

```
curl --request GET 'localhost:8080/dogs'
```

- create a new dog

```
curl -H "Content-Type: application/json" --request POST 'localhost:8080/dogs'  -d '{"name":"NEW DOG"}'
```

- show the dog

```
curl --request GET 'localhost:8080/dogs/1'
```

- update the dog

```
curl -H "Content-Type: application/json" --request PUT 'localhost:8080/dogs/1'  -d '{"name":"Updated DOG"}'
```

- delete the dog

```
curl --request DELETE 'localhost:8080/dogs/1'
```

# 参考


- [Routing DSL for HTTP servers](http://doc.akka.io/docs/akka/2.4.10/scala/http/introduction.html#Routing_DSL_for_HTTP_servers)
    - Sample.scalaで使用 
- [knoldus/akka-http-slick](https://github.com/knoldus/akka-http-slick)
    - Akka HTTPとSlickの連携で参考にさせて頂きました。 

# まとめ

こうしてみると、かなり少ない記述でDB連携からAPIの実装までできたことが分かるかと思います。
ちょっとScalaとっつきにくいぜ、と思ってる方の手助けになれば幸いです。
自分もまだまだ勉強しなきゃですね。