Kotlin + Googleアシスタントでアクションのバックエンドを作ってみる

# はじめに
2019/01/16にActions on Google(AoG)Client LibraryのJava/Kotlin版が発表されました :tada: 

- [Announcing the Java & Kotlin client library for Actions on Google](https://medium.com/google-developers/announcing-the-java-kotlin-client-library-for-actions-on-google-217828dead) 
- [actions-on-google/actions-on-google-java: Java/Kotlin library for Actions on Google](https://github.com/actions-on-google/actions-on-google-java)
 - [JavaDoc](https://actions-on-google.github.io/actions-on-google-java/)
 

公式では、Kotlin + [Google App Engine上のJavaサーブレット](https://cloud.google.com/appengine/docs/flexible/java/quickstart#top_of_page)で構築する方法が紹介されています。

- [actions-on-google/dialogflow-webhook-boilerplate-java: Boilerplate to help you get started quickly with the Java client library for Actions on Google.](https://github.com/actions-on-google/dialogflow-webhook-boilerplate-java)

KotlinにはKtorというWebフレームワークがあります。
今回はフルKotlinでGoogleアシスタントのアクションを構築する方法を紹介します。 

# 使う技術
次の表に、各言語で使う技術構成をまとめました。 
GoogleアシスタントのバックエンドはNode.js + Cloud Functions For Firebaseの構成で作られることが多いです。
今回は、Kotlin + Ktor + Google App Engine(GAE)で作ります！ :muscle: 
Ktor, GAEは環境構築の一番最初の部分から解説していきます。

|   言語  |  フレームワーク | 実行環境| 
| --- | --- | --- | 
|  Kotlin   |  [Ktor](https://ktor.io/)   | PaaS: [Google App Engine](https://cloud.google.com/appengine/?hl=ja)
| Node.js | ([Express](https://expressjs.com/ja/)) | FaaS: [Cloud Functions For Firebase](https://firebase.google.com/docs/functions/?hl=ja) |

## Ktorについて
<img src="https://d3nmt5vlzunoa1.cloudfront.net/kotlin/files/2018/11/Ktor.png" width=250>

[Ktor](https://ktor.io/) とは、Kotlinの軽量で簡単に使えるWebフレームワークです。
おなじみJetBrains社が開発をしており、安心感があります。

去年(2018)の11月にv1.0がリリースされており、Kotlinサーバサイドでの利用が期待が高まっています。
- [Ktor 1.0 Released: A Connected Applications Framework by JetBrains | Kotlin Blog](https://blog.jetbrains.com/kotlin/2018/11/ktor-1-0/)

# 構築手順
## Actions on GoogleプロジェクトとDialogflowエージェントの作成
こちらは従来どおりの作り方と変わりありません。 

## 開発環境の準備(テンプレートを利用)
- https://github.com/1coin178/actions-on-google-kotlin-ktor-boilerplate  

↑のGihtubリポジトリにテンプレートを作成しておきました。環境構築の際にはぜひお使いください！
ダウンロード後、Intellij IDEで開いてください。

## AoG Java/Kotlinクライアントライブラリの使い方
次に本題のAoG Java/Kotlinクライアントライブラリの使い方を解説していきます。

AoG Java/Kotlinクライアントライブラリは、
`build.gradle` に↓を指定することでダウンロードしています。

```gradle
compile "com.google.actions:actions-on-google:1.0.0"
```

### IntentHandler作成
Dialogflowからのリクエストを受け取り処理するためのIntentHandlerを作成します。 

新規に`ConversationComponentsApp.kt`を作成してください。
IntentHandlerの作成ポイントは

1. [DialogflowApp](https://actions-on-google.github.io/actions-on-google-java/com/google/actions/api/DialogflowApp.html)を継承させる。
2. [ForIntent](https://actions-on-google.github.io/actions-on-google-java/com/google/actions/api/ForIntent.html)アノテーションを使ってDialogflowで定義したIntent名と処理を紐付ける。
3. [ResponseBuilder](https://actions-on-google.github.io/actions-on-google-java/com/google/actions/api/response/ResponseBuilder.html)を使ってレスポンスを組み立てる。

コードはこんな感じです。

```kotlin

class ConversationComponentsApp : DialogflowApp() {

    @ForIntent("Default Welcome Intent")
    fun welcome(request: ActionRequest): ActionResponse {
        val responseBuilder = getResponseBuilder(request)
        responseBuilder
            .add(SimpleResponse()
                .setDisplayText("純情Action!")
                .setTextToSpeech("純情Action!Action!"))
        return responseBuilder.build()
    }

    @ForIntent("Good Bye")
    fun goodBye(request: ActionRequest): ActionResponse {
        val responseBuilder = getResponseBuilder(request)
        responseBuilder
            .add("会話を終了します。さようなら")
            .endConversation()    // 会話を終了させる。 Node.js版でのconv.close
        return responseBuilder.build()
    }
}
```

例では[SimpleResponse](https://developers.google.com/actions/assistant/responses#simple_responses)を使っていますが、他に[BasicCard](https://developers.google.com/actions/assistant/responses#basic_card)や[Carousel](https://developers.google.com/actions/assistant/responses#carousel)、[Table](https://developers.google.com/actions/assistant/responses#table_card)などのサンプルを公式がGithubあげてくれています。
気になる方は、こちら↓も見てみるといいでしょう！

- https://github.com/actions-on-google/dialogflow-conversation-components-java/blob/master/src/main/java/com/example/ConversationComponentsApp.java



### POSTリクエストのエンドポイント用意
続いて、作ったIntentHandlerにDialogflowからのデータを渡せるようにしましょう。
DialogflowからはPOSTリクエストでJSONが送られてくるので、次のコードの通りに `/` をルートパスとしてJSONを受け取ります。 

`call.receiveText()`でJSONデータを受け取り、
先程作ったした `ConversationComponentsApp#handleRequest`にヘッダー情報と一緒に渡します。
これによりIntent名に対応してレスポンスが返されます。 
最後に、`call.respond` を使ってDialogflow側に返却してあげます。

```Kotlin
fun Application.module(testing: Boolean = false) {
    val actionsApp= ConversationComponentsApp()
    routing {
        post("/"){
            // body取得
            val jsonBody: String = call.receiveText()
            // header取得
            val headerMap  = getHeaderMap(call.request)
            // IntentHandlerにbody,header情報を渡す
            val jsonResponse = actionsApp.handleRequest(jsonBody, headerMap).get()
            call.respond(jsonResponse)
        }
    }
}

// ヘッダー情報をHashMapにKey-Valueでつっこんであげる。
fun getHeaderMap(request: ApplicationRequest): Map<String, String>{
    val headers= request.headers
    val headerMap = HashMap<String, String>()
    headers.forEach{ k, v -> headerMap[k] = v.toString() }
    return headerMap
}
```

## GAEへのデプロイ準備 
### ターミナル編
Google Cloud SDKをMacにインストールし、初期化まで完了させます。

- 参考:[Mac OS X 用のクイックスタート  |  Cloud SDK のドキュメント  |  Google Cloud](https://cloud.google.com/sdk/docs/quickstart-mac-os-x?hl=ja)

初期化中にGoogle Cloud Platformプロジェクトを選択する必要があります。Actions on GoogleプロジェクトIDと同じものを選びましょう。
<img src='https://qiita-image-store.s3.amazonaws.com/0/86747/49197ad5-936a-b49c-6e4e-151808fc4037.png' width=450 />


設定できたら、現在の設定に間違いないか確認しておくといいでしょう。

```bash
$ gcloud config list
[app]
promote_by_default = true
[core]
account = <AoGプロジェクト作成時と同じGoogleアカウントのメールアドレス>
disable_usage_reporting = False
project = <Actions on GoogleのプロジェクトID(GCP Project id)>
```

GAEのJavaプラグインをインストールします。

```bash 
$ gcloud components install app-engine-java
$ gcloud components update
```

### Intellij IDE編
GAE関連の設定を解説します。

`build.gradle`に↓を記入しておくことで、GAE用のGradleタスクが活用できるようになります。

```gradle
buildscript {
    ... 
    dependencies {
        ...
        classpath "com.google.cloud.tools:appengine-gradle-plugin:1.3.4"
    }
}

...
apply plugin: 'war'
... 

dependencies {
    ...
    providedCompile "com.google.appengine:appengine:1.9.60"
}

appengine {  
    // ご自身の環境に合わせて変更してください。
    tools.cloudSdkHome="<google-cloud-sdkディレクトリへのパス>"
}
```

テンプレートコードのディレクトリ構成を整理しておきます。
ディレクトリツリー構造はこんな感じになっています。

```bash 
...
.
└── main
    ├── kotlin
    │   ├── Application.kt
    │   └── ConversationComponentsApp.kt
    ├── resources
    │   ├── application.conf
    │   └── logback.xml
    └── webapp
        └── WEB-INF
            ├── appengine-web.xml
            ├── logging.properties
            └── web.xml
```

kotlinファイルは、`src/main/kotlin`に、プロジェクトルートに存在するktorの設定ファイルが`src/main/resources`にあります。
`src/main/webapp`というディレクトリがあるのですが、詳細については↓を参考にしてみてください:bow: 

- [構成ファイル  |  Java の App Engine スタンダード環境  |  Google Cloud](https://cloud.google.com/appengine/docs/standard/java/configuration-files?hl=ja)
- https://github.com/ktorio/ktor-samples/tree/master/deployment/google-appengine-standard


## GAEへデプロイ！ と動作確認！
ここまでの設定が完了したら、
`./gradlew appengineDeploy`かIDEのGradleタスクから`appengineDeploy`を実行してください。 
成功すると、`https://<gcp-project-id>.appspot.com`のURLがコンソールに表示されるので、
こちらをメモっておき、DialogflowのFullfillmentに設定すれば完了です！ 

```bash
...
Deployed service [default] to [https://<gcp-project-id>.appspot.com]
...
```