次の案件でKotlinを使おうと思って勉強を始めました。
資料
とりあえず申込、契約管理の業務を想定したDDDのサンプルを作りながら勉強中(コードはいつか公開します)
今日のハイライト
Spring Initializrで簡単にテンプレートが作れた
Kotlinを選択すればSpring Bootプロジェクトのサンプルがダウンロードできる。
sealed class
詳細は上記リンクを見ていただくとして、sealed classを使って「申込がないときは申込できる(すでに申込済みのときは申込できない)」という業務ロジックをこんな感じで表現してみました。
/**
* 申込エンティティ
*/
class OrderEntity(val order: Order) {
/**
* 申し込む
* note : Eitherがないので例外をスローしている。できればエラーを表現した型で返却したい。
*
* @param ordAt : 申込日
* @return 申込イベント
* @throws RuntimeException 申込ではないとき
*/
fun order(ordAt: OrdAt): OrderEvent = when (order) {
is Order.NotExist -> OrderEvent(ordAt)
is Order.Progressing -> throw RuntimeException("already ordered")
is Order.Completed -> throw RuntimeException("already completed")
}
}
/**
* 申込
*/
sealed class Order {
class NotExist : Order()
class Progressing(val id: Id, val ordAt: OrdAt) : Order()
class Completed(val id: Id, val completeAt: CompleteAt) : Order()
}
/**
* 申込イベント
*/
data class OrderEvent(val ordAt: OrdAt)
/**
* 取消イベント
*/
data class CancelEvent(val id: Id, val cancelAt: CancelAt)
/**
* 完了イベント
*/
data class CompleteEvent(val id: Id, val completeAt: CompleteAt)
data class Id(val value: Int)
data class OrdAt(val value: LocalDateTime)
data class CancelAt(val value: LocalDateTime)
data class CompleteAt(val value: LocalDateTime)
when句の中でsealed classの実装クラスを網羅できていないとコンパイルエラーにしてくれるところがイケてると思いました。
例:上記のコードをこうすると
fun order(ordAt: OrdAt): OrderEvent = when (order) {
is Order.NotExist -> OrderEvent(ordAt)
is Order.Progressing -> throw RuntimeException("already ordered")
// is Order.Completed -> throw RuntimeException("already completed")
}
以下のようなコンパイルエラーになる
$ ./gradlew clean compileKotlin :clean :compileKotlin e: /Users/******/Develop/git/ddd-sample-kotlin/src/main/kotlin/work/doilux/dddsamplekotlin/ftth/order/Order.kt: (29, 43): 'when' expression must be exhaustive, add necessary 'is Completed' branch or 'else' branch instead :compileKotlin FAILED FAILURE: Build failed with an exception.
Nullable
Nullが入る可能性があるところは?をつけた型にしておくとNullチェックがない場合はコンパイルエラーになります。 以下のように使いました。
/**
* 参照用のクラス。 型の後の?はNullableを表す。
*/
data class OrderState(
val id: Int,
val status: String,
val orderAt: LocalDateTime?,
val cancelAt: LocalDateTime?,
val completeAt: LocalDateTime?
)
Either、Optionはない
Scalaと違ってEither、Optionはなさげ。とりあえず、郷に入れば郷に従えでOptionについてはNullableで頑張ってみます(peekとか使いたくなりそう)Eitherは、ドメイン表現については前述のsealed classを使った実装もありですが、エラー返却でほしい(ドメイン層で例外スローは絶対やめたい。。。)Kotlinで作るか、vavrを使うかは考えます。