BlogBracken

FOR_MYSELF

ExposedでDBの起動を待ってからDatabase.connectする

docker-composeで抱き合わせるときに使えるスニペット.

TL;DR

接続先のデータベース(今回はMariaDB)は環境に応じて書き換えてください.

private const val INTERVAL = 1_000
private const val TRIALS = 5

fun initialize(host: String, database: String, user: String, password: String) {
    val url = "jdbc:mariadb://$host/$database"

    fun getConnectionOrNull(): Connection? =
        try {
            val properties = Properties().apply {
                put("user", user)
                put("password", password)
            }

            DriverManager.getConnection(url, properties)
        } catch (ignored: SQLException) {
            null
        }

    fun tryConnectBlocking(trials: Int): Connection? =
        (0 until trials).asSequence()
            .map { getConnectionOrNull() ?: Thread.sleep(INTERVAL) }
            .filterIsInstance<Connection>()
            .firstOrNull()

    Database.connect(getNewConnection = {
        tryConnectBlocking(TRIALS) ?: throw IllegalAccessException("Couldn't connect to database!")
    })
}

動機

Ktor + Exposedにてdocker-composeを用いてMariaDBと同時に立ち上げることがあり, その時に先走ってExposedのDatabase.connectが呼ばれてクラッシュするのを避けたかった次第. 公式によるとアプリケーション側で制御しろとのことだったので*1, それに従い上のようなコードを書いた.

tryConnectBlockingで接続までのブロッキングを行っている訳だが, mapの返り値の型がConnectionUnitの直和型ではなく Any型となってしまう所に若干の気持ち悪さが無いといえば嘘になる.*2

*1:https://docs.docker.com/compose/startup-order/

*2:そう思ってしまうのは良いとも良くないとも思う