Kotlin-DSLなどKotlinでLiquibaseを使うためのライブラリを作った
※大事なことなので2回書きました
課題
- Kotlinを使っているプロダクトで、LiquibaseによるマイグレーションでもKotlinの文法が使えるようになったら、Kotlinでのコーディングと同じノリで書けるようになるため、Kotlinエンジニアにとってはありがたいが、そういうライブラリがあまり無い。
- 今まであったLiquibaseのKotlin-DSLは、kotlin-scriptでの実行であった。そのため、IDEででの補完が効かないことがあった。
- 今までのLiquibaseのKotlin-DSLがメンテされていない。
- Liquibaseがコマンドラインベースでの使用を想定されていて、プログラマブルに実行をする方法が分からない
- データマイグレーションなど、DBマイグレーションの中で、ORMを使いたいケースがある
(ちなみに)各DBマイグレーションツールについて雑にまとめ
Flyway: SQLで書くもの。
Liquibase: XMLで管理。JSON. YAML, SQL、Groovyでも書けるが、JSONやYAMLだと子要素という概念が無くプロパティのみで読みにくいので、XMLかGroovy-DSL、または今回作ったKotlin-DSLが良さそう。
Harmonica: Railsライク。メンテナンスされてなさそう。
Exposed-migration: 履歴管理のためのテーブルが無いらしく、各自で工夫が必要
解決策(作ったもの)
こちらです。
ドキュメントは、こちら
機能としては、以下の通りです。
- Liquibaseを実行できるKotlinのラッパー
- kotlin-scriptと通常のコンパイルするKotlinでマイグレーションを書くことができる、Kotlin-DSLのモジュール
- Liquibaseのマイグレーションの中で、ORMを使うことができるモジュール
CIにも工夫というか努力をしており、毎日SNAPSHOT版のLiquibaseでのテストを定期実行するようにしています。非公式のLiquibaseのDSLは、最新のLiquibaseに追いついていないという課題がだいたいあるため、ここは重要なところだと思います。
また、私の作成したプロダクトのバージョン名にLiquibaseのバージョンを含めることで、対応しているLiquibaseのバージョンがわかりやすいようにしております。
そして、Kotlin-scriptであってもIntelliJ IDEAでの補完が効くようにはなっております。
セットアップ
build.gradle.ktsに以下追加します
dependencies {
implementation("org.liquibase:liquibase-core:4.29.2")
implementation("io.github.momosetkn:liquibase-kotlin-starter-compiled:4.29.2-0.8.0")
}
kotlin-scriptを使いたい場合は、以下となります
dependencies {
implementation("org.liquibase:liquibase-core:4.29.2")
implementation("io.github.momosetkn:liquibase-kotlin-starter-script:4.29.2-0.8.0")
}
これから紹介するLiquibaseClientを使うのであれば、基本的には、liquibase-kotlin-starter-compiled
で良いかなとは思います。
今までのファイルシステム上のファイルを読み取って使うように使っているのであれば、liquibase-kotlin-starter-script
を使うことになると思います。
使い方
コンパイルされたマイグレーションでの実行を説明します。
configureLiquibaseで、Liquibaseのグローバルな設定を書きます。
LiquibaseDatabaseFactory.create
で、接続先のデータベースを定義し、LiquibaseClient
へchangeLogFileを渡して、マイグレーション実行となります。
import momosetkn.liquibase.client.LiquibaseDatabaseFactory
import momosetkn.liquibase.client.configureLiquibase
import momosetkn.liquibase.kotlin.parser.KotlinCompiledDatabaseChangeLog
import momosetkn.liquibase.client.LiquibaseClient
fun main() {
// Liquibaseのグローバルな設定
// 各設定値については以下公式ドキュメントを!
// https://docs.liquibase.com/parameters/home.html
configureLiquibase {
global {
general {
showBanner = false
}
}
}
// データベースをセットする
val database = LiquibaseDatabaseFactory.create(
driver = "com.mysql.cj.jdbc.Driver",
url = "jdbc:mysql://localhost:3306/test_db",
username = "root",
password = "",
)
// changeLogのファイルとデータベースを指定
val client = LiquibaseClient(
changeLogFile = example.DatabaseChangelog20241007Employee1::class.qualifiedName!!,
database = database,
)
// マイグレーション実行!
client.update()
}
class DatabaseChangelog20241007Employee1 : KotlinCompiledDatabaseChangeLog({
changeSet(author = "your_name", id = "20241007-2000-1") {
createTable(tableName = "employee") {
column(name = "id", type = "UUID") {
constraints(nullable = false, primaryKey = true)
}
column(name = "company_id", type = "UUID") {
constraints(nullable = false)
}
column(name = "name", type = "VARCHAR(256)")
column(name = "not_null_name", type = "VARCHAR(256)") {
constraints(nullable = false)
}
column(name = "not_null_name2", type = "VARCHAR(256)") {
constraints(nullable = false)
}
}
}
})
include/includeAll
includeやincludeAllでは、パスを指定しますが、コンパイルされたKotlin(拡張子がkt)で書く場合にはパッケージ名・クラス名が使えます。
kotlin-script(拡張子がkts)から、コンパイルされたKotlinのchangeLogを呼び出す場合、クラスパス上での名前の指定となるため、.を/へ置き換えて、クラスの指定のために.classをつけます
コンパイルされたKotlinの場合
import momosetkn.liquibase.kotlin.parser.KotlinCompiledDatabaseChangeLog
class DatabaseChangelogAll : KotlinCompiledDatabaseChangeLog({
// コンパイルされたKotlin(拡張子がkt)の場合、パッケージ名・クラス名で書く
includeAll("example.migration")
include("example.migration2.Example")
})
kotlin-scriptの場合
databaseChangeLog {
// kotlin-script(拡張子がkts)の場合、クラスパス上での名前を書く
includeAll("example/migration")
include("example/migration2/Example.class")
}
ORMをマイグレーションの中で使う
Komapper(JDBC)
build.gradle.ktsに以下追加する
dependencies {
implementation("io.github.momosetkn:liquibase-kotlin-custom-komapper-jdbc-change:4.29.2-0.8.0")
}
そうすると、以下のようにマイグレーションの中でKomapperでのマイグレーションが実行できるようになります。
import momosetkn.liquibase.kotlin.parser.KotlinCompiledDatabaseChangeLog
import momosetkn.liquibase.kotlin.change.custom.komapper.customKomapperJdbcChange
import org.komapper.core.dsl.QueryDsl
class CompiledDatabaseChangelog1 : KotlinCompiledDatabaseChangeLog({
changeSet(author = "your_name", id = "20241007-2000-1") {
customKomapperJdbcChange(
execute = { db ->
val query = QueryDsl.executeScript(
"""
CREATE TABLE created_by_komapper (
id uuid NOT NULL,
name character varying(256)
);
""".trimIndent()
)
db.runQuery(query)
},
rollback = { db ->
val query = QueryDsl.executeScript("DROP TABLE created_by_komapper")
db.runQuery(query)
},
)
}
})
また、kotlin-scriptの場合でも使うことができます。QueryDslがデフォルトインポートされているため、QueryDslをimport無しで使うことができます。
databaseChangeLog {
changeSet(author = "your_name", id = "20241007-2000-1") {
customKomapperJdbcChange(
execute = { db ->
val query = QueryDsl.executeScript(
"""
CREATE TABLE created_by_komapper (
id uuid NOT NULL,
name character varying(256)
);
""".trimIndent()
)
db.runQuery(query)
},
rollback = { db ->
val query = QueryDsl.executeScript("DROP TABLE created_by_komapper")
db.runQuery(query)
},
)
}
}
Exposed-migration
build.gradle.ktsに以下追加する
dependencies {
implementation("io.github.momosetkn:liquibase-kotlin-custom-exposed-migration-change:4.29.2-0.8.0")
}
そうすると、以下のようにマイグレーションの中でExposed-migrationでのマイグレーションが実行できるようになります。
import momosetkn.liquibase.kotlin.parser.KotlinCompiledDatabaseChangeLog
import momosetkn.liquibase.kotlin.change.custom.exposed.customExposedMigrationChange
import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.Table
import org.jetbrains.exposed.sql.transactions.transaction
class CompiledDatabaseChangelog1 : KotlinCompiledDatabaseChangeLog({
changeSet(author = "your_name", id = "20241007-2000-1") {
// https://jetbrains.github.io/Exposed/table-definition.html#dsl-create-table
val createdByExposed = object : Table("created_by_exposed") {
val id = integer("id").autoIncrement()
val name = varchar("name", 256)
override val primaryKey = PrimaryKey(id)
}
customExposedMigrationChange(
execute = { db ->
transaction(db) {
SchemaUtils.create(createdByExposed)
}
},
rollback = { db ->
transaction(db) {
SchemaUtils.drop(createdByExposed)
}
},
)
}
})
また、kotlin-scriptの場合でも使うことができます。org.jetbrains.exposed.sql.*
とorg.jetbrains.exposed.sql.transactions.transaction
がデフォルトインポートされています。
databaseChangeLog {
changeSet(author = "your_name", id = "20241007-2000-1") {
// https://jetbrains.github.io/Exposed/table-definition.html#dsl-create-table
val createdByExposed = object : Table("created_by_exposed") {
val id = integer("id").autoIncrement()
val name = varchar("name", 256)
override val primaryKey = PrimaryKey(id)
}
customExposedMigrationChange(
execute = { db ->
transaction(db) {
SchemaUtils.create(createdByExposed)
}
},
rollback = { db ->
transaction(db) {
SchemaUtils.drop(createdByExposed)
}
},
)
}
}
他のORM
現在、他のORMだとjOOQやktormの拡張もサポートしております。
dependencies {
// jOOQ
implementation("io.github.momosetkn:liquibase-kotlin-custom-jooq-change:4.29.2-0.8.0")
// Ktorm
implementation("io.github.momosetkn:liquibase-kotlin-custom-ktorm-change:4.29.2-0.8.0")
}
Discussion