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


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

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

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

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

無料の割り当てについての公式情報はこちら

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

スタンダード環境とフレキシブル環境の違いについての公式情報はこちら

Ktor

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

Gradle

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

参考文献

この記事で紹介している内容は、こちらの記事の内容が元になっています。
Run a Kotlin Ktor app on App Engine standard environment

前準備

参考文献Before you begin 節にある前準備を行います。

  • JDK8をインストールします。
  • Google Cloud SDKをインストールします。
  • Google Cloud Platform(GCP)のプロジェクトを作り、App Engineアプリケーションを作成します。
    • 公式解説
    • 次の「今回使用したgcloudコマンド」節も参考にしてください。
  • プロジェクトIDの名前を PROJECT_ID 環境変数に設定します。
  • 課金の有効化を行います。これをやらないとデプロイ出来ません。公式解説

今回使用したgcloudコマンド

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

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

リージョンを聞かれます。特にこだわりがなければ近いところを選択します。asia-northeast1が東京です。

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

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

ダウンロードリンク

また、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 をクリックします。
GradleKotlin/JVMをチェックして Next ボタンをクリックします。

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

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ファイルを作成します。

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 ]
    }
}

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

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

./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を使うと、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 ディレクトリを作成します。

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>

こちらの内容は参考文献で紹介されているこちらのXMLファイルです。

gradleでデプロイする

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

./gradlew appengineDeploy

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

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

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

補足

Autoreload

ローカルサーバについてソースコードを変更したらすぐに反映させる方法があります。
Ktor + GradleでのAutoreload

全体ソースコード

Githubで公開しています。