Akka Httpを試してみる

# 前置き
ScalaのWebフレームワークを触ってみる第2弾として、今回は[Akka Http](https://doc.akka.io/docs/akka-http/current/index.html) を軽〜く触ってみます。
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で読み込むと次のように色々ファイル群が生成されています。
![スクリーンショット 2020-09-26 14.20.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7572/09912d18-ac98-b702-c615-abfbb46a366a.png)

`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 に対する処理は実装されていませんので、対応するルーティングをちょっと記述します。

```scala:UserRoutes
val userRoutes: Route =
    pathSingleSlash { // 追加ここから
      get {
        complete((StatusCodes.NotFound, "hello"))
      }
    } ~ // 追加ここまで
    pathPrefix("users") { // 以下省略 }
```
これで http://localhost:8080 にアクセスすると "hello"が表示されます。

#### 1. POST /users
```scala:この部分
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`ケースクラスにマッピングしてくれます。

```scala
implicit val userJsonFormat = jsonFormat3(User)
```

レスポンス

```
{
  "description": "User 山田太郎 created."
}

Response code: 201 (Created); Time: 21ms; Content length: 36 byte
```

#### 2. GET /users
```scala:この部分
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にパースしてくれます。

```scala
implicit val usersJsonFormat = jsonFormat1(Users)
```

#### 3. GET /users/{name}
```scala:この部分
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}
```scala:この部分
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の方が個人的にはわかり易かったです。