前置き
ScalaのWebフレームワークを触ってみる第2弾として、今回はAkka Http を軽〜く触ってみます。
Akka Http は正確にはフレームワークではなく、 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.
事前準備
Akka Httpを動かすのに必要な次のものを事前にインストールしてください。今回は次の通り。
- Java 11
- Scala 2.13.3
- sbt 1.3.13
Akka Httpプロジェクト作成
好きなところにディレクトリを作成して次のコマンドを実行
sbt new akka/akka-http-quickstart-scala.g8
対話形式で色々聞かれるので今回は次の構成とします。
name [My Akka HTTP Project]: akka-http-sample
scala_version [2.13.3]:
akka_http_version [10.2.0]:
akka_version [2.6.9]:
organization [com.example]:
package [com.example]:
作成されたプロジェクトをIntelliJで読み込むと次のように色々ファイル群が生成されています。
JsonFormats
JSONのリクエスト/レスポンスをフォーマットして任意のcase classにマッピングしてくれる。QuickStartの例だと、User
とUsers
について定義されている。
※jsonFormatN()
のNは対象case classの引数の数
QuickstartApp
サーバーの起動に関するメイン処理。ここでActorの起動や、Routeの指定をしている。
UserRegistry
Routeで処理している各種Userに関するCRUD処理を定義している。
UserRoutes
Routing定義
動作確認
実際に動かして確認してみます。
起動する
IntelliJで QuickstartApp
のmainを実行するとサーバが http://localhost:8080 で起動します。
リクエストを投げてみる
UserRoutes
には、既にユーザーに対する処理が実装されています。それぞれに対応するリクエストを投げてみます。
0. いきなり番外編
http://localhost:8080 に対する処理は実装されていませんので、対応するルーティングをちょっと記述します。
val userRoutes: Route =
pathSingleSlash { // 追加ここから
get {
complete((StatusCodes.NotFound, "hello"))
}
} ~ // 追加ここまで
pathPrefix("users") { // 以下省略 }
これで http://localhost:8080 にアクセスすると "hello"が表示されます。
1. POST /users
post {
entity(as[User]) { user =>
complete(createUser(user))
onSuccess(createUser(user)) { performed =>
complete((StatusCodes.Created, performed))
}
}
}
リクエスト
POST http://localhost:8080/users
Content-Type: application/json
{
"name": "山田太郎",
"age": 18,
"countryOfResidence": "Japan"
}
JsonFormats
の次の定義を解釈してJSONをUser
ケースクラスにマッピングしてくれます。
implicit val userJsonFormat = jsonFormat3(User)
レスポンス
{
"description": "User 山田太郎 created."
}
Response code: 201 (Created); Time: 21ms; Content length: 36 byte
2. GET /users
get {
complete(getUsers())
}
リクエスト
GET http://localhost:8080/users
Accept: application/json
レスポンス
{
"users": [
{
"age": 18,
"countryOfResidence": "Japan",
"name": "山田太郎"
},
{
"age": 22,
"countryOfResidence": "Japan",
"name": "田中次郎"
}
]
}
Response code: 200 (OK); Time: 694ms; Content length: 119 bytes
こちらも、JsonFormats
の次の定義を解釈してUsers
ケースクラスをJSONにパースしてくれます。
implicit val usersJsonFormat = jsonFormat1(Users)
3. GET /users/{name}
path(Segment) { name => //path(Segment)でリクエスト中の{name}の文字列を取得している
concat(
get {
//#retrieve-user-info
rejectEmptyResponse {
onSuccess(getUser(name)) { response =>
complete(response.maybeUser)
}
}
//#retrieve-user-info
},
リクエスト
GET http://localhost:8080/users/山田太郎
Accept: application/json
レスポンス
{
"age": 18,
"countryOfResidence": "Japan",
"name": "山田太郎"
}
Response code: 200 (OK); Time: 23ms; Content length: 53 bytes
4. DELETE /users/{name}
path(Segment) { name =>
concat(
{ // 省略・・・
},
delete {
//#users-delete-logic
onSuccess(deleteUser(name)) { performed =>
complete((StatusCodes.OK, performed))
}
//#users-delete-logic
}
)
}
リクエスト
DELETE http://localhost:8080/users/山田太郎
レスポンス
{
"description": "User 山田太郎 deleted."
}
Response code: 200 (OK); Time: 689ms; Content length: 36 bytes
まとめ
ほんとに軽〜く触ってみましたが、Quickstartで生成されるサンプルにAkkaやActorの処理が入り込んでいるので、パッと見はとっつきづらい印象がありますね。軽いREST APIを書くのであれば Scalatraの方が個人的にはわかり易かったです。