【自分向け】Kotlinことはじめ
以前軽く触ったことはあったが、改めて環境構築。
基本的に自分用メモです。
使用マシンは、M1 Macbook Air。
まずはHomebrewを使ってインストール。
$ brew install java
その後パスを通す。
.zshrc
export PATH=/opt/homebrew/opt/openjdk/bin:$PATH
参考
Gradleを使うので、インストール。
$ brew install gradle
Kotlin周りはIntelliJ IDEAに付属しているっぽいので、インストール。
CEでいいのかな...?
$ brew install --cask intellij-idea-ce
余談ですが、cask はいつの間にかオプションになったみたいです。
とりあえず以前他のマシンで適当に作ったこれを動かすところまでやってみる。
わりと最近作ったのにもう動かし方を覚えていない...。
少しおさらいから。
Kotlinについて
KotlinはJetBrains社が作った言語。
そのため、JetBrains社のIDEであるIntelliJ IDEAを入れると環境構築ができるというワケ。
特徴としては、Javaの拡張言語的な立ち位置で同居できること。
Javaからの移行先言語として採用されることも多い。
ざっくり言うと、「Javaが動く環境で動くJavaよりイケてる言語」という感じ。多分。
Javaについて
オブジェクト指向でおなじみの言語Java。
大きく分けて2種類ある様子。
- Open JDK
- Oracle JDK
とりあえず今後はOpen JDKを使っとけばOKって感じ?
あれ、これこっちでインストールしたほうがいいのか...?
$ brew install openjdk
ひとまず動かすところまでやってから考える。
参考
Gradleについて
NodeJSにおけるpackage.json的なもの?
パッケージ管理とか、ビルドとか、諸々のタスク管理とかできる感じ?
Groovyという言語で build.gradle
ファイルを作成するのが従来のやり方で、Kotlinを使った場合は、 代わりに build.gradle.kts
ファイルを作成して利用することができる。
記述方法は若干の違いがあるが、基本的には同じようなことができるはず。
参考
- https://qiita.com/vvakame/items/83366fbfa47562fafbf4
- https://qiita.com/opengl-8080/items/a0bb31fb20cb6505188b
Gradlewについて
Gradlewは、特定のGradleのバージョンを指定して、そのプロジェクトの開発者全員のGradleのバージョンを統一させることができるものである。
そのため、プロジェクトのgitに含めて利用する。
Gradleをインストールした状態で、下記のコマンドを実行することで、gradlew用のファイル一式を生成できる。
$ gradle wapper
wrapper
タスクの記載が不要になった模様。
普通に作っちゃってたので、後で消そう...。
参考
ひとまず
$ ./gradlew run
で動いた。
けど、JSON返すところまで作ってなかったっぽい。
どこまでやったかすら覚えてなかった...。
さすがにJSON返すところまでは作ります。
ひとまず動くところまで完成。
内容のまとめは気が向いたらやる。
VSCode派だったけど、IntelliJ IDEA使ってみたら、Kotlinやるなら外せないなという感じがあった。
あとで型周りを調べて、アーキテクチャ整理する。
アーキテクチャ周りのSeedWorkをちょっとだけ整理して、1ユースケース追加。
ルーティングのプレースホルダーは、下記のように取得できるっぽい。
route("/users/{id}") {
get {
val id = call.parameters["id"]
// ...
}
}
型がnull許容になるのが微妙なので、あとでなにかいい方法がないか調べる。
参考
POSTリクエストでBodyを取得する場合は、receive
を使うのが良い様子。
post {
val request = call.receive<AddUserUsecaseRequest>()
// ...
}
ジェネリクスで指定するクラスをdata classにして、@Serializable
アノテーションをつけておく必要がありそうなので注意。
@Serializable
data class AddUserUsecaseRequest(
val name: String,
val email: String,
): UsecaseRequest
参考
UUIDの生成は非常に簡単だった。
val newUserId = UUID.randomUUID().toString()
DIフレームワークを試してみる。
Koinが良さそうなので、とりあえずこれを採用。
2.x系と3.x系があるようで、group idが io.insert-koin
のほうが新しいっぽい?
多分こんなノリでOK。
build.gradle.kts
implementation("io.insert-koin:koin-core:3.0.1")
うーん、3.x系の情報が少なくていまいち動かない...。
これも必要そう...?
build.gradle.kts
implementation("io.insert-koin:koin-core-ext:3.0.1")
implementation("io.insert-koin:koin-ktor:3.0.1")
implementation("io.insert-koin:koin-logger-slf4j:3.0.1")
Kodeinに切り替えてやってみる。
できた。
Koinより簡単だった印象。
dependenciesには、Ktor用のモジュールを追加。
build.gradle.kts
implementation("org.kodein.di:kodein-di-framework-ktor-server-jvm:7.3.1")
DI設定はこんな感じ。
instance()
で内包するインスタンスもよしなにDIしてくれる模様。
fun DI.MainBuilder.bindServices() {
bind<UsersRepository>() with singleton { UsersRepositoryImpl() }
bind<FindAllUsersUsecase>() with singleton { FindAllUsersUsecaseImpl(instance()) }
bind<AddUserUsecase>() with singleton { AddUserUsecaseImpl(instance()) }
bind<FindUserByIdUsecase>() with singleton { FindUserByIdUsecaseImpl(instance()) }
}
サーバー側の設定はこんな感じ。
di {
bindServices()
}
インスタンスを使う側はこう。
val findAllUsersUsecase by di().instance<FindAllUsersUsecase>()
val addUserUsecase by di().instance<AddUserUsecase>()
val findUserByIdUsecase by di().instance<FindUserByIdUsecase>()
Koinで苦戦したのはなんだったんだ...。
バージョン3.xを使おうとしたのが時期が悪かったかな。
暫定Kodein採用でいきます。
参考
Router = Controllerとして、再整理。
ユースケース単位でクラス分割された、MVC+レイヤードアーキテクチャっぽくなってきた。
ErrorHandling周りをあとでやる。
Result
型とrunCatching
によるハンドリングもあるみたいなので、レイヤーとか返すレスポンスによって使い分けてもいいかも。
これだけでハンドリングしていないExceptionをキャッチしてくれるっぽい。
install(StatusPages) {
exception<Throwable> {
call.respond(HttpStatusCode.InternalServerError)
}
}
全部500で処理するわけにもいかないので、クライアントエラーの機構は実装してみる。
APIリクエストのバリデーションあたりを組み込めるか調査する。