Androidエンジニアが慣れている技術で作る維持費0円のサーバサイドアプリケーション

# 今回紹介する技術スタックの紹介

普段Androidアプリを作っていますが、たまに認証周りなどでサーバサイドの技術検証をすることがあります。そのときよく使う技術スタックがこちらです。

## Google App Engine スタンダード環境(Java8)

- Javaサーブレッドをデプロイできます。
- アクセスがなければ0円で維持できます。
    - 久しぶりのアクセスだとコールドスタートになり、レンスポンスに5秒くらいかかります。
- アクセスがあっても1インスタンスで収まる少ないアクセス数ならば0円で維持できます。

[無料の割り当てについての公式情報はこちら](https://cloud.google.com/appengine/quotas?hl=ja#Instances)

ちなみにスタンダード環境の他にフレキシブル環境というものもありますが、そちらは0円で維持できません。

[スタンダード環境とフレキシブル環境の違いについての公式情報はこちら](https://cloud.google.com/appengine/docs/java?hl=ja)

## Ktor

KotlinとCoroutineでHTTPサーバが作れます。Androidの人が普段お世話になっているKotlin言語の記述力を活かしたフレームワークです。
所謂SinatraライクなWebフレームワークです。

## Gradle

ビルドしてローカルサーバーを起動したり、Google App Engineにデプロイ出来ます。

# 参考文献

この記事で紹介している内容は、こちらの記事の内容が元になっています。
[Run a Kotlin Ktor app on App Engine standard environment](https://cloud.google.com/community/tutorials/kotlin-ktor-app-engine-java8)

# 前準備

[参考文献](https://cloud.google.com/community/tutorials/kotlin-ktor-app-engine-java8)の `Before you begin` 節にある前準備を行います。

- JDK8をインストールします。
- Google Cloud SDKをインストールします。 
    - [公式解説](https://cloud.google.com/sdk/docs?hl=ja)
- Google Cloud Platform(GCP)のプロジェクトを作り、App Engineアプリケーションを作成します。 
    - [公式解説](https://cloud.google.com/appengine/docs/standard/java/console?hl=ja#gcloud)
    - 次の「今回使用したgcloudコマンド」節も参考にしてください。
- プロジェクトIDの名前を `PROJECT_ID` 環境変数に設定します。
- 課金の有効化を行います。これをやらないとデプロイ出来ません。[公式解説](https://cloud.google.com/appengine/docs/standard/java/console?hl=ja#billing)

## 今回使用したgcloudコマンド

`tfandkusu-qiita-gae` の部分は各自でグローバルでユニークな名前を設定します。

```sh
# GCPのプロジェクトを作る
gcloud projects create tfandkusu-qiita-gae
# App Engineアプリケーションを作成する
gcloud app create --project tfandkusu-qiita-gae
```

リージョンを聞かれます。特にこだわりがなければ近いところを選択します。[asia-northeast1が東京です。](https://cloud.google.com/compute/docs/regions-zones?hl=ja#locations)

# IntelliJ IDEA Community Editionをインストールする

もしあなたが普段Androidアプリを開発しているとしたら、Android Studioがインストールされていると思います。しかし今回はAndroidではない素のGradleプロジェクトを作ります。その機能はAndroid Studioに無いので、IntelliJ IDEA Community Editionをインストールします。

[ダウンロードリンク](https://www.jetbrains.com/ja-jp/idea/download)

また、Android StudioとIntelliJ IDEAは共存できます。Android Studio 4.0.1とIntelliJ IDEA Community Edition 2020.1.4で確認しています。

# IntelliJ IDEAでGradleプロジェクトを作成する

IntelliJ IDEA Community Editionを起動します。
`Create New Project` をクリックします。
`Gradle` → `Kotlin/JVM`をチェックして `Next` ボタンをクリックします。

![スクリーンショット 2020-08-22 23.34.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334798/12ecc494-753c-a6f7-c0f9-0f81dae97d55.png)

プロジェクト名、ディレクトリなどを設定して、 `Finish` ボタンをクリックします。

![スクリーンショット 2020-08-22 23.35.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334798/13f7c84a-6984-305a-a52d-4562b1a0aa6c.png)

# Ktorを使えるようにする。

## Ktorライブラリを追加

このようにプロジェクトルートの `build.gradle` ファイルを編集します。

```build.gradle
// 略
repositories {
    mavenCentral()
    // 追加
    jcenter()
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
    // 追加
    // ktorのバージョン
    def ktor_version = '1.4.0'
    // ローカルテストサーバ起動
    implementation "io.ktor:ktor-server-netty:$ktor_version"
    // サーブレットを作る
    implementation "io.ktor:ktor-server-servlet:$ktor_version"
    // HTMLをKotlinでレンダリングする
    implementation "io.ktor:ktor-html-builder:$ktor_version"
}
// 略
```

## Hello Worldを作る

メインとなるKotlinファイルを置くパッケージを作成して、そこにKotlinファイルを作成します。

![スクリーンショット 2020-08-23 0.08.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334798/493e20b2-7513-6aa0-0b83-f50aeaf79306.png)

Kotlinファイルの内容です。

```Application.kt
package com.tfandkusu.qiitagae

import io.ktor.application.Application
import io.ktor.application.call
import io.ktor.html.respondHtml
import io.ktor.routing.get
import io.ktor.routing.routing
import kotlinx.html.body
import kotlinx.html.head
import kotlinx.html.p
import kotlinx.html.title

fun Application.main() {
    routing {
        get("/") {
            call.respondHtml {
                head {
                    title { +"Hello World" }
                }
                body {
                    p {
                        +"Hello World"
                    }
                }
            }
        }
    }
}
```

## ローカルサーバを起動できるようにする

### Gradleアプリケーションプラグインを設定する

build.gradleの最後にこちらを追加します。

```build.gradle
apply plugin: 'application'
mainClassName = 'io.ktor.server.netty.EngineMain'
```

### Ktorの設定ファイルを追加する

`src/resources/application.conf` を追加します。

```src/resources/application.conf
ktor {
    deployment {
        // ローカルサーバ起動ポート番号
        port = 8080
    }
    application {
        // HelloWorldを作ったパッケージ名付きのKotlinソースコード名にKt.mainを付ける
        modules = [ com.tfandkusu.qiitagae.ApplicationKt.main ]
    }
}
```

## ローカルサーバを起動する

前述の設定でこちらのコマンドでローカルサーバを起動できるようになりました。

```sh
./gradlew run
```

ブラウザで http://localhost:8080/ にアクセスすると、Hello Worldが表示されます。

# エラーが出るようにする

このままだと、配列の領域外アクセス等の実行時エラーが発生しても標準出力にはなにも表示されず、デバッグ困難です。　

## logback-classic ライブラリを追加する

`logback-classic` ライブラリを追加することで、標準出力にエラーが表示されるようになります。

```build.gradle
// 略
dependencies {
    // 略
    // 追加
    // エラーが出るようにする
    implementation "ch.qos.logback:logback-classic:1.2.3"
}
// 略
```

## エラーが標準出力に出ることを確認する

```Application.kt
fun Application.main() {
    routing {
        get("/") {
            val a = mutableListOf<Int>()
            a[0] = 1
            // 略
        }
    }
}
```

`./gradlew run` を実行してブラウザで http://localhost:8080/ にアクセスすると、コンソールにスタックトレースが出ることを確認出来ます。

```
00:30:26.371 [nioEventLoopGroup-4-1] ERROR Application - Unhandled: GET - /
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
        at java.util.ArrayList.rangeCheck(ArrayList.java:657)
        at java.util.ArrayList.set(ArrayList.java:448)
```

# Google App Engineにデプロイする

## Google App Engine Gradle pluginを設定する

### 導入

[Google App Engine Gradle plugin](https://github.com/GoogleCloudPlatform/app-gradle-plugin)を使うと、1コマンドでビルドしてGoogle App Engineにデプロイすることが出来ます。

```build.gradle
// 先頭に追加
buildscript {
    repositories {
        mavenCentral()
    }

    dependencies {
        classpath 'com.google.cloud.tools:appengine-gradle-plugin:2.3.0'
    }
}
// 元からある部分
plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.3.72'
}
// 2つのプラグインを追加
apply plugin: 'war'
apply plugin: 'com.google.cloud.tools.appengine'
```

### 設定

バージョン名とGCPのプロジェクトIDはbuild.gradleで設定します。

```build.gradle
// 最後に追加
// Google App Engineの設定
appengine {
    deploy {
        // バージョン名
        version = "a1"
        // GCPのプロジェクトID
        projectId = System.getenv()['PROJECT_ID']
    }
}
```


## Google App EngineとJavaサーブレットの設定を行う

### 設定ファイルを置くディレクトリを作成する

`src/main/webapp/WEB-INF` ディレクトリを作成します。

![スクリーンショット 2020-08-23 1.42.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334798/b259ae23-7baa-0ad9-a6b3-ba2f3b6e4961.png)

### Google App Engineの設定ファイルを作成する

```src/main/webapp/WEB-INF/appengine-web.xml
<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
    <threadsafe>true</threadsafe>
    <runtime>java8</runtime>
</appengine-web-app>
```

### Javaサーブレットの設定ファイルを作成する

```src/main/webapp/WEB-INF/web.xml
<?xml version="1.0" encoding="ISO-8859-1" ?>
<web-app>
    <servlet>
        <display-name>KtorServlet</display-name>
        <servlet-name>KtorServlet</servlet-name>
        <servlet-class>io.ktor.server.servlet.ServletApplicationEngine</servlet-class>
        <!-- path to application.conf file, required -->
        <init-param>
            <param-name>io.ktor.config</param-name>
            <param-value>application.conf</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>KtorServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>
```

こちらの内容は[参考文献](https://cloud.google.com/community/tutorials/kotlin-ktor-app-engine-java8)で紹介されている[こちらのXMLファイル](https://github.com/GoogleCloudPlatform/community/blob/master/tutorials/kotlin-ktor-app-engine-java8/web-example.xml)です。

## gradleでデプロイする

gradleのappengineDeployタスクでデプロイ出来ます。

```sh
./gradlew appengineDeploy
```

完了したら、こちらのコマンドでブラウザが開き、デプロイされたWebアプリケーションにアクセスすることが出来ます。　

```sh
# tfandkusu-qiita-gae の部分は各自で作成したGCPのプロジェクトIDにします。
gcloud app browse --project=tfandkusu-qiita-gae
```

これで解説は終了です。
あとは検証や作成したい内容に応じて、ライブラリやソースコードを追加していきます。

# 補足

## Autoreload

ローカルサーバについてソースコードを変更したらすぐに反映させる方法があります。
[Ktor + GradleでのAutoreload](https://qiita.com/tfandkusu/items/e810ae3bfa75c39e6b37)


# 全体ソースコード

[Githubで公開しています。](https://github.com/tfandkusu/qiita_gae)