doilux’s tech blog

ITに関する備忘録。 DDP : http://doiluxng.hatenablog.com/entry/2018/01/01/195409

Kotlin勉強メモ(その1)

次の案件でKotlinを使おうと思って勉強を始めました。

資料

とりあえず申込、契約管理の業務を想定したDDDのサンプルを作りながら勉強中(コードはいつか公開します)

今日のハイライト

Spring Initializrで簡単にテンプレートが作れた

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を使うかは考えます。