Kotlin Advent Calendar 2021の23日目です。
あんまりKotlinの深い話ではないのですが、SpringBootのアプリケーションをKotlinで書いたときの感想などを書きたいと思います。
内容は少し薄いかもしれませんが、それでも誰かの目に止まって同じようなことをしようとしている人の参考にしてもらえると嬉しいです。
自分の状況
2018年まではJavaで主に業務用のWebアプリケーションを作っておりました。
Tomcatで動かすWARベースのアプリケーションや、SpringBoot(当時は1.3くらい)でWebアプリケーションを実装した経験があります。
その後、Androidのサービスを開発する会社に転職してAndroidアプリ開発はKotlinで行っていることもあり、ここ3年ほどJavaやSpringBootから離れておりました。
そんな中、副業で簡単なコマンドアプリを作る必要があり提供されるJavaのSDKを使う必要があったので、起動時間などの制約などもなくSpringBootを使うことにしました。
2018年のときはわからなかったことなど多かった記憶がありますが、2018年から3年経過した場合の変化は凄まじく、思った以上にKotlinで開発することができました。
簡単なコマンドアプリケーションだったので、業務用のような複雑なことをしておりませんが、基本的な簡単な使い方などについて書きたいと思います。
お断りになりますが、JavaやSpringBootのキャッチアップができていないので、新しいバージョンだったら違う方法で簡単にできるよ!みたいなこともあるかもしれませんが、ご了承いただけると幸いです。
環境
InteliJ IDEA Ultimate
SpringBoot 2.6.1
Kotlin 1.6
KotlinでSpringBootで開発する方法
好きなエディタなりIDEを使えばよいとは思いますが、自分はKotlinを使う場合、IDEはInteliJ IDEAを使っております。
KotlinもIntelliJ IDEAもJetBrainsなので、こだわりがない場合はInteliJ IDEAを使ってみることをおすすめします。
DIでインジェクションする方法
一番最初にKotlinでDIするときはどうするのだろう思います。
Javaのときは最初@Autowiredを使っており、その後LombokでRequiredArgsConstructor等のコンストラクタインジェクションを使っておりました。
できればコンストラクタで状態を確定させることができるコンストラクタインジェクションを使いたいです。
Kotlinではどうやるかというと・・・
[code lang=java]
@Repository
class HogeRepository(
private val jdbcTemplate: JdbcTemplate
)
[/code]
いやー、素晴らしいですね。
Java8時代はLombokがないとコンストラクタインジェクションだとコードが冗長になる部分もあるのですが、Kotlinだと簡単ですね。
JdbcTemplateのqueryForObjectでdata classを方法
JdbcTempleteのqueryForObjectでデータベース等の検索結果を任意のオブジェクトにマッピングできます。
Kotlinの場合、言い方が難しいですがエンティティなどのJava Beanはdata classを使うことが多いと思います。
サンプルコードでよく記載されているBeanPropertyRowMapperではdata classはマッピングできないと思われます。
queryForObjectのときにdata classにマッピングする場合は、以下のようにします。
[code lang=java]
data class NameEntity(val name: String)
jdbcTemplate.queryForObject(sql, DataClassRowMapper(NameEntity::class.java))
[/code]
いやー、素晴らしいですね。
小ネタでjdbcTemplateを初めて使ったのですがqueryForObjectの戻り値がnullableだったので、レコードが取得できなかった場合nullが帰ってくるのかと思ったら、例外が送出されるんですね。
EmptyResultDataAccessExceptionですので注意が必要です。
ConfigurationPropertiesでdata classを使う方法
プロパティの内容を参照するときにConfigurationPropertiesでJavaBeanにマッピングすることがよくあると思います。
この場合もdata classで定義したいのですが、data classはそのままではマッピングできません。
例えば、以下のようなプロパティファイルの場合は以下のようにします。
[code lang=yaml]
app:
version: 1
[/code]
[code lang=java]
@ConstructorBinding // このアノテーションをつける
@ConfigurationProperties(prefix = ”app”)
data class AppConfig(
val version: Int
)
[/code]
いやー、素晴らしいですね。
data classでマッピングできると本当に楽ですね。
なお、設定によって変更できるのかもしれませんが@ConfigurationPropertiesを使う場合、Applicationクラスに@ConfigurationPropertiesScanをつける必要があるみたいです。
テスト時にSpy等をする方法
続いてテストです。
Service等に依存するUsecaseがあった場合、ユニットテスト時はServiceの挙動を変えたいということがよくあると思います。
色々なやり方があると思うのですが、今回はmockkとspringmockkを使いました。
[code lang=java]
@SpringBootTest
class UsecaseTest {
@SpykBean
private lateinit var service: HogeService
private lateinit var usecase: Usecase
@BeforeEach
fun beforeEach() {
usecase = Usecase(service)
// touch()は戻り値がないメソッド、returnsはUnitにする
every { service.touch(any()) } returns Unit
every { service.fetch(any()) } returns NameEntity(name = ”hoge”)
}
@Test
fun `何かをテストする`() {
}
}
[/code]
もしかしたら、コンストラクタインジェクションを使ってMockなりSpyなりを差し込む方法もあるかもしれません。
mockkはMockitoと若干書き方が違う部分もあるかもしれませんが、個人的にはMockitoをkotlinで使おうとするとwhenが非常に可読性が悪く感じたのでmockkを使っています。
わからなかったこと
APTがある場合
今回、IntelliJでプロジェクトを作成するとbuild.gradle.ktsが作成されてびっくりしました。
簡単なコマンドアプリだったのでAPT(KAPT)系は使わなかったのですが、APTを使う必要がある場合、もしかしたらbuild.gradleのときにできていたことができない可能性もあるので事前に検証が必要かと思います。
テストのアサーション
JavaのときはAssertJを使っていたのですが、Kotlinだとアサーションは何がよいのか自分の中でもまだ答えがないです。
applyを使って違う書き方ができると思われますが可読性が高いかというと個人差があると思います。
[code lang=java]
data class Person(name: String, age: Int)
Person(name = ”Hoge”, age = 10).apply {
assertEquals(name, ”Hoge”)
assertEquals(age, 10)
}
[/code]
結びに
SpringのKotlinサポートが思った以上に手厚く行われていることに驚きました。
2018年に動作確認用にKotlinでサーバーサイドのアプリケーションをSpringBootを書いたことがありますが、当時はKotlinの経験もそれほどなかったので本格導入は行なえませんでしたが時代の変化は素晴らしいですね。
Javaもどんどん進化していっていると思いますが、Kotlinも本当に書きやすい言語なので、今後もSpringでサポートが手厚く行われてほしいと思います。
この記事が、KotlinでSpringBootを書こうと思っている人のお役にたてると嬉しいです。
参考
【SpringJdbc】DataClassRowMapperを用いコンストラクタ呼び出しでマッピングを行う【Java/Kotlin】
Spring Boot 2.2系で@ConfigurationPropertiesを使う