Full stack kotlin プロジェクトで開発をはじめて1週間くらいたった感想

会社の新しいプロジェクトの実装を今のところ1人でやっている。Androidのクライアントアプリはもちろん、フロントエンドのWebページとかバックエンドのサーバー、 Cloud Function と言った物の実装も必要で、それならばいっそのこと自分の好きなkotlinを使って実装をしてみようかと思って挑戦をしている。いわゆる Full stack kotlin プロジェクト。

プロジェクト構成作りと手を付けられる部分の実装を並行して進めていて、1週間くらい経ってプロジェクトの構成がまとまったのでここまでの感想を書いてみる。

1年後、同じようにkotlinを Full stack に利用しているかどうかはわからないが、それならばそれで今の気持ちを思い返すことで何が駄目だったのかが分かる。あるいは、1年後にプロジェクトそのものが閉じていた場合には、それはそれで Full stack kotlin プロジェクトに敗因があったのかもしれないと反省できる。いずれにせよ、少なくとも1年後の自分のための備忘録として役立つはずという気持ちでこの記事を書く。

## プロジェクトの構成

ソースコードやパッケージを明かすことは今は、やりたくない（経営判断）なのでディレクトリや利用しているサービスが分かる程度に。

```bash
├── android
│   ├── android.iml
│   ├── app
│   │   ├── app.iml
│   │   ├── build
│   │   │   ├── generated
│   │   │   ├── intermediates
│   │   │   ├── kotlin
│   │   │   ├── outputs
│   │   │   ├── reports
│   │   │   └── tmp
│   │   ├── build.gradle
│   │   ├── cred.gradle
│   │   ├── proguard-rules.pro
│   │   └── src
│   │       ├── androidTest
│   │       ├── main
│   |       |   └── kotlin
│   │       └── test
│   └── build.gradle
├── appengine
│   ├── Dockerfile
│   ├── appengine.iml
│   ├── build
│   │   ├── classes
│   │   │   └── kotlin
│   │   ├── libs
│   │   │   └── appengine.jar
│   │   └── tmp
│   │       └── jar
│   ├── build.gradle
│   ├── node_modules
│   ├── package.json
│   └── src
│       └── main
│           └── kotlin
├── build.gradle
├── common-js
│   ├── build
│   │   ├── classes
│   │   │   └── kotlin
│   │   ├── libs
│   │   │   └── common-js.jar
│   │   └── tmp
│   │       └── jar
│   ├── build.gradle
│   ├── common-js.iml
│   └── src
│       └── main
│           └── kotlin
├── firebase
│   ├── build.gradle
│   ├── firebase.iml
│   ├── firebase.json
│   ├── functions
│   │   ├── build
│   │   │   ├── classes
│   │   │   ├── libs
│   │   │   ├── public
│   │   │   └── tmp
│   │   ├── build.gradle
│   │   ├── functions.iml
│   │   ├── index.js
│   │   ├── package.json
│   │   └── src
│   │       └── main
│   |            └── kotlin
│   └── public
│       ├── build
│       │   ├── classes
│       │   ├── libs
│       │   ├── public
│       │   └── tmp
│       ├── build.gradle
│       ├── firebase-debug.log
│       ├── index.html
│       ├── package-lock.json
│       ├── package.json
│       ├── public.iml
│       ├── src
│       │   └── main
│       ├── webpack.common.js
│       ├── webpack.config.js
│       └── wepack.dev.js
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── local.properties
└── settings.gradle
```

プロジェクト自体はgradleで管理を行っている。サブプロジェクトは、`settings.gradle`に記述を行った物を利用している。

```groovy
rootProject.name = 'himitsu'
include ':common-js'
include ':firebase', ':firebase:public', ':firebase:functions'
include ':android', ':android:app'
include ':appengine'
```

内訳を詳しく見てみよう。

### common-js

kotlin.js を利用したプロジェクトを構成しているので共通化可能な部分についてはこのライブラリプロジェクトにまとめている。具体的には`Promise`をラップしてkotlinのcoroutineに変換するものとか。また、共通のデータエンティティやjsonへのシリアライザーもここにある。他のプロジェクトからは　`compile project(":common-js")`を使って参照する。

### firebase

バックエンドとフロントエンドにfirebaseを利用している。

#### firebase:public

Hosting を利用してフロントエンドのWebページを作った。利用している技術スタックはいくつかある。

- React
    - コンテンツ作成のために利用している。バックエンドとして起動している`appengine`のAPIを呼び出してコンテンツの描画を行わせる
- Kotlin.js
    - React をkotlinでラップした[kotlin-react](https://github.com/JetBrains/kotlin-wrappers/tree/master/kotlin-react)と[kotlin-react-dom](https://github.com/JetBrains/kotlin-wrappers/tree/master/kotlin-react-dom)を利用している。
- Webpack
    - `require`を解決するために利用している。最初はbrowserifyを使っていた。Kotlin.jsのビルド結果が`build/dependencies`以下に展開されて、node_modulesと一緒に運用するのがbrowserifyを利用するとうまく行かなかったのでwebpackに変えた。

#### firebase:functions

バックエンドとして利用している Cloud Functions for Firebase のためのプロジェクト。技術的なスタックとしては何も考えずにnode.jsを使っているのだけれど、これもKotlin.jsのビルド結果が`build/classes/kotlin/main/dependencies`いかに展開されるので、build.gradleに次のようなコードを書いている。ビルド結果を無理やり`node_modules`以下にコピーしている。

```groovy
task copyKotlinJs(type: Copy, dependsOn: compileKotlin2Js) {
    def workDir = "$buildDir/classes/kotlin/main/dependencies"
    print workDir
    from(workDir) {
        include "*.js"
    }
    into "$projectDir/node_modules/"
}

afterEvaluate {
    copyDependenciesKotlinJs.dependsOn copyKotlinJs
    assemble.dependsOn copyDependenciesKotlinJs
}
```

このやり方が正しいのかどうかは知らない。ちなみに、 Cloud Functions for Firebase のエントリーポイントは`index.js`となってしまうため、次のコードを書いてルーティングだけをするコードとしている。

```js
const functions = require('firebase-functions')
const admin = require('firebase-admin')
const functionKt = require("./build/classes/kotlin/main/functions.js")
admin.initializeApp(functions.config().firebase)

exports.search = functionKt.com.covelline.himitsu.search
```
### appengine

バックエンドは Cloud Functions for Firebase 以外にも Google Appengine も使っている。というのも、Webページのスクレイピングが必要でそのためには puppeteer を利用するのがてっとり早そうだったから。というわけでこちもらKotlin.jsを利用している。サーバー機能はexpress。Custom Runtimes for the Flexible Environmentを使っているのでエントリーポイントは自由にできるのがうれしい。そんなわけでpackage.jsonの一部はこんな感じ。

```json
  "scripts": {
    "start": "NODE_PATH=build/classes/kotlin/main/dependencies node build/classes/kotlin/main/appengine.js",
    "watch-start": "NODE_PATH=build/classes/kotlin/main/dependencies nodemon build/classes/kotlin/main/appengine.js"
  }
```

### android

これは特に特別なことはない。

## 感想

### 良かったこと

好きな言語であるkotlinを利用したコードを書くことができている点が一番気持ち良い。あと、開発環境を Android Studio 一本で行ける点も良い。言語が統一されているので、コンテキストスイッチも不要。[以前やってたサービス](http://blog.covelline.com/entry/2016/09/29/181054)は、javascriptがメインだったけど、Rubyが入り込んでいたりしたのでコンテキストスイッチが必要だった。

共通化とかについては今はあんまり深くは考えていないけれど、将来的にやり用はあるなと思っている。

ビルド時間も遅くなるかな？と思ったけど、言語のコンパイル自体は別に遅くなかったし、気にならない程度。今のところ遅いと感じているのは、

- Android (しょうがない)
- Webpack

って感じ。Android Studioのサブプロジェクトだけのビルドにショートカットを割り当てていないのでマウス操作が発生している。これは、ショートカットの割当をするだけで解決するのでそのうちやる。

あと、やっぱり型がちゃんとあってコンパイルされる言語である点はjavascriptを書く上で有利だと思った。javascriptのモジュールの全てに対して型を割り当てているわけではないけれど、主だって利用する部分は自分で`@JsModule`アノテーションを使って`object`や`class`を宣言している。そのため、ドキュメントがしっかりと整っているモジュールについては型の間違えに起因するコードのミスは無くなった。

### 良くないこと

どうしても同じ名前のファイルがいくつもでてきてしまう。これはkotlinが原因ではなくて、一つのリポジトリで作業をしていることが原因なんだけど。Android Studioのファイルを開くショートカット(`Cmd + Shift + O`)を使ってファイルをタイプすると一応どのサブプロジェクトに所属しているのか表示されるので助かる。しかし、その補完処理が遅いのかEnterキーを押す瞬間に別のプロジェクトのファイルにフォーカスが当たることがある。いや、それを開きたいわけじゃねえんだよと。

あと、プロジェクトを作るときにはどうしても試行錯誤があった。1週間でまとまったのは奇跡に近い。嘘です、[JetBrains/kotlinconf-app: KotlinConf Schedule Application](https://github.com/JetBrains/kotlinconf-app/)をかなりパクりました。ただ、複数のマシンで作業をしていると`.iml`ファイルの内容とプロジェクトの構成が違うので、`git pull`をしたときにAndroid Studioで正しくプロジェクトをロードできない現象は起きた。そのときは、すべての`.iml`ファイルの削除とAndrid Studioの再起動でことなきを得た。これはまあ、初期には発生する問題かもしれない。

あと、未調査だけどRectの中でjsxなら本来はビルドできないコードがビルドできたような気がする。気のせいかもしれない。

### 杞憂

alt-jsとしてkotlinを利用している点が一番気になる。今後、プロジェクトが進展していく中でチームへの展開を正しく行うことができるのかどうかと言った不安は残る。それもふまえて、こういう文章を残しているのだけれど。

## 今後

一応、デメリットよりもメリットが上回っているのでこのままこのプロジェクト構成を継続しようと思う。大きく方向転換が発生したら続編の記事を書くかも。あと、色々とまとまったらプロジェクト構成をOSSとして公開したい。