Skip to content

Commit

Permalink
Feature/flyway migrations (#26)
Browse files Browse the repository at this point in the history
  • Loading branch information
agile-jordi committed Jan 19, 2024
2 parents 0fc7821 + 79e1946 commit c57bee5
Show file tree
Hide file tree
Showing 13 changed files with 113 additions and 101 deletions.
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ build:
test:
./gradlew test

.PHONY: format
format:
./gradlew --continue ktlintFormat

.PHONY: deploy
deploy:
./gradlew build -x test -x check
Expand Down
5 changes: 0 additions & 5 deletions apps/migrations/Dockerfile

This file was deleted.

This file was deleted.

This file was deleted.

5 changes: 5 additions & 0 deletions apps/migrationsapp/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
FROM openjdk:17.0.1-jdk-slim

COPY build/libs/migrationsapp-1.0-SNAPSHOT.jar migrationsapp.jar

CMD [ "java", "-jar" , "migrationsapp.jar"]
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Dependencies.flywayCore

import Dependencies.kotestRunnerJunit
import Dependencies.postgresql
import Dependencies.slf4jProvider
Expand Down Expand Up @@ -33,7 +33,7 @@ application {
dependencies {
implementation(slf4jProvider)
implementation(postgresql)
implementation(flywayCore)
implementation(project(":migrations"))
implementation(project(":db"))
implementation(project(":postgresdb"))
implementation(project(":herokupostgres"))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.agilogy.timetracking.migrationsapp

import arrow.continuations.SuspendApp
import com.agilogy.db.hikari.HikariCp
import com.agilogy.heroku.postgres.loadHerokuPostgresConfig
import com.agilogy.timetracking.migrations.runMigrations

fun main() = SuspendApp {
val (jdbcUrl, username, password) = loadHerokuPostgresConfig()
val dataSource = HikariCp.dataSource(jdbcUrl, username, password)
dataSource.use { runMigrations(it) }
}
19 changes: 19 additions & 0 deletions components/migrations/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Dependencies.flywayCore
import Dependencies.hikariCp
import Dependencies.kotestRunnerJunit
import Dependencies.postgresql
import Dependencies.slf4jProvider

dependencies {
implementation(project(":domain"))
implementation(project(":db"))
api(flywayCore)
implementation(slf4jProvider)

testImplementation(hikariCp)
testImplementation(postgresql)
testImplementation(kotestRunnerJunit)
testImplementation(testFixtures(project(":domain")))

testFixturesImplementation(project(":domain"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.agilogy.timetracking.migrations

import com.agilogy.timetracking.migrations.scripts.Migration202306231200
import kotlinx.coroutines.runBlocking
import org.flywaydb.core.Flyway
import org.flywaydb.core.api.MigrationVersion
import org.flywaydb.core.api.configuration.FluentConfiguration
import org.flywaydb.core.api.migration.Context
import org.flywaydb.core.api.migration.JavaMigration
import org.slf4j.LoggerFactory
import java.sql.Connection
import javax.sql.DataSource

abstract class KotlinMigration(private val version: String, private val title: String) : JavaMigration {

override fun getVersion(): MigrationVersion = MigrationVersion.fromVersion(version)

override fun getDescription(): String = title

override fun getChecksum(): Int? = null

override fun canExecuteInTransaction(): Boolean = true

final override fun migrate(context: Context) = runBlocking {
with(context.connection!!) {
migrate()
}
}

context(Connection)
abstract suspend fun migrate()
}

fun runMigrations(dataSource: DataSource, clean: Boolean = false) {
val flywayConfig: FluentConfiguration = Flyway.configure()
.dataSource(dataSource)
.group(true)
.outOfOrder(false)
.baselineOnMigrate(true)
.locations("classpath:${Migration202306231200::class.java.packageName.replace('.', '/')}")
.loggers("slf4j")

val validated = flywayConfig
.ignoreMigrationPatterns("*:pending")
.load()
.validateWithResult()

if (!validated.validationSuccessful) {
val logger = LoggerFactory.getLogger("RunMigrations")
for (error in validated.invalidMigrations) {
logger.warn(
"""
|Failed to validate migration:
| - version: ${error.version}
| - path: ${error.filepath}
| - description: ${error.description}
| - error code: ${error.errorDetails.errorCode}
| - error message: ${error.errorDetails.errorMessage}
""".trimMargin("|").trim(),
)
}
}
if (clean) {
flywayConfig.cleanDisabled(false).load().clean()
}
println(flywayConfig.load().migrate())
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class Migration202306231200 : KotlinMigration("202306231200", "Initial migration
|"end" timestamptz not null,
|zone_id text not null
|)
""",
""".trimMargin(),
)
}
}
1 change: 1 addition & 0 deletions components/postgresdb/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ dependencies {
testImplementation(postgresql)
testImplementation(kotestRunnerJunit)
testImplementation(testFixtures(project(":domain")))
testImplementation(project(":migrations"))

testFixturesImplementation(project(":domain"))
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,6 @@ class PostgresTimeEntriesRepository(private val dataSource: DataSource) : TimeEn

private fun ResultSetView.project(columnIndex: Int): ProjectName? = string(columnIndex)?.let { ProjectName(it) }

companion object {
val dbMigrations = listOf(
"""create table time_entries(
|id serial,
|developer text not null,
|project text not null,
|start timestamptz not null,
|"end" timestamptz not null
)
""".trimMargin(),
"""alter table time_entries add column zone_id text not null default 'Europe/Madrid'""",
"""alter table time_entries alter column zone_id drop default""",
)
}

override suspend fun saveTimeEntries(timeEntries: List<TimeEntry>) = dataSource.sql {
val sql = """insert into time_entries(developer, project, start, "end", zone_id) values (?, ?, ?, ?, ?)"""
batchUpdate(sql) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import com.agilogy.timetracking.domain.ProjectName
import com.agilogy.timetracking.domain.TimeEntriesRepository
import com.agilogy.timetracking.domain.TimeEntry
import com.agilogy.timetracking.domain.test.InMemoryTimeEntriesRepository
import com.agilogy.timetracking.migrations.runMigrations
import io.kotest.core.spec.style.FunSpec
import io.kotest.core.test.TestScope
import org.junit.jupiter.api.Assertions.assertEquals
Expand Down Expand Up @@ -41,14 +42,7 @@ class TimeEntriesRepositoryTest : FunSpec() {
}

withTestDataSource { dataSource ->
println("Recreating table time_entries")
kotlin.runCatching { dataSource.sql { Sql.update("drop table time_entries") } }
.recoverIf(Unit) { it is PSQLException && it.sqlState == PostgreSql.UndefinedTable }.getOrThrow()
PostgresTimeEntriesRepository.dbMigrations.forEach { dbMigration ->
dataSource.sql {
Sql.update(dbMigration)
}
}
runMigrations(dataSource, clean = true)
f(PostgresTimeEntriesRepository(dataSource))
}
}
Expand Down

0 comments on commit c57bee5

Please sign in to comment.