Kora облачно ориентированный серверный фреймворк написанный на Java для написания Java / Kotlin приложений с упором на производительность, эффективность, прозрачность сделанный выходцами из Т-Банк / Тинькофф

Kora is a cloud-oriented server-side Java framework for writing Java / Kotlin applications with a focus on performance, efficiency and transparency

Skip to content

Migration

Database migrations apply schema and reference data changes in a controlled order: they create tables, indexes, constraints, and perform other SQL operations required by a new application version. In Kora, migration modules are bound to JdbcDatabase initialization through a GraphInterceptor<JdbcDatabase>: when the application starts, JdbcDatabase is created as a graph component, and the interceptor's init() runs migrations before the component is published to the rest of the graph. If a migration fails, init() throws, so JdbcDatabase component initialization and the whole graph build (application startup) fail as well. The interceptor's release() is a no-op: migrations are never rolled back or re-run when the application stops.

This approach is convenient for local development, tests, and small installations where the application runs as a single instance. For environments with multiple replicas, choose a separate migration execution method in advance so migrations are not run simultaneously from every application instance. Repositories do not create the database schema themselves: tables, indexes, constraints, and reference data must be created by migrations or by an external database preparation process.

Flyway

Module for database migration using the Flyway tool. During JdbcDatabase initialization, the module calls Flyway.migrate() with settings from the flyway section. Migrations are run by FlywayJdbcDatabaseInterceptor, which is provided by FlywayJdbcDatabaseModule. Flyway is wired to SLF4J (loggers("slf4j")), so migration output and the FlyWay migration applied in ... timing line (logged at INFO) appear in the application's normal logs.

Dependency

Dependency build.gradle:

implementation "ru.tinkoff.kora:database-flyway"

Module:

@KoraApp
public interface Application extends FlywayJdbcDatabaseModule { }

Dependency build.gradle.kts:

implementation("ru.tinkoff.kora:database-flyway")

Module:

@KoraApp
interface Application : FlywayJdbcDatabaseModule

Requires the JDBC module because migrations are executed through DataSource. Applications usually include both modules: JdbcDatabaseModule creates JdbcDatabase, and FlywayJdbcDatabaseModule adds the migration interceptor.

Configuration

Example of the complete configuration described by the FlywayConfig class:

flyway {
    enabled = true //(1)!
    locations = ["db/migration"] //(2)!
    executeInTransaction = true //(3)!
    validateOnMigrate = true //(4)!
    mixed = false //(5)!
    configurationProperties {} //(6)!
}
  1. Enables migration execution during JdbcDatabase initialization (default: true). If set to false, the module skips the Flyway.migrate() call.
  2. Paths to directories with migration scripts (default: ["db/migration"]).
  3. Executes migrations inside a transaction when supported by the database and the SQL operations themselves (default: true).
  4. Validates checksums of already applied migrations before executing new ones (default: true). If checksums do not match, startup fails with an error.
  5. Allows mixing transactional and non-transactional SQL operations in one migration (default: false). If enabled, the whole migration is executed without a transaction to avoid errors in databases where some operations cannot run inside a transaction. This setting is relevant for databases that do not support executing certain operations inside a transaction: PostgreSQL, Aurora PostgreSQL, SQL Server, and SQLite.
  6. Additional Flyway key-value properties (default: {}). Use them to pass settings that do not have a separate Kora configuration option, such as schemas, baselineOnMigrate, placeholderReplacement, or placeholders.*.
flyway:
    enabled: true #(1)!
    locations: ["db/migration"] #(2)!
    executeInTransaction: true #(3)!
    validateOnMigrate: true #(4)!
    mixed: false #(5)!
    configurationProperties: {} #(6)!
  1. Enables migration execution during JdbcDatabase initialization (default: true). If set to false, the module skips the Flyway.migrate() call.
  2. Paths to directories with migration scripts (default: ["db/migration"]).
  3. Executes migrations inside a transaction when supported by the database and the SQL operations themselves (default: true).
  4. Validates checksums of already applied migrations before executing new ones (default: true). If checksums do not match, startup fails with an error.
  5. Allows mixing transactional and non-transactional SQL operations in one migration (default: false). If enabled, the whole migration is executed without a transaction to avoid errors in databases where some operations cannot run inside a transaction. This setting is relevant for databases that do not support executing certain operations inside a transaction: PostgreSQL, Aurora PostgreSQL, SQL Server, and SQLite.
  6. Additional Flyway key-value properties (default: {}). Use them to pass settings that do not have a separate Kora configuration option, such as schemas, baselineOnMigrate, placeholderReplacement, or placeholders.*.

Migration Files

By default, Flyway looks for migrations in src/main/resources/db/migration. A regular migration file has a name like V1__init_schema.sql, where V1 is the version and the part after the double underscore is the description.

src/main/resources/db/migration/
  V1__init_users.sql
  V2__add_user_status.sql

Example of a simple migration:

CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    name TEXT NOT NULL
);

When Flyway starts, it creates a service migration history table and applies only new versions. If validateOnMigrate is enabled, already applied files must not be changed without a separate migration history repair process.

Liquibase

Module for database migration using the Liquibase tool. During JdbcDatabase initialization, the module obtains a connection from DataSource, creates a Liquibase instance, and calls update(). Migrations are run by LiquibaseJdbcDatabaseInterceptor, which is provided by LiquibaseJdbcDatabaseModule.

Dependency

Dependency build.gradle:

implementation "ru.tinkoff.kora:database-liquibase"

Module:

@KoraApp
public interface Application extends LiquibaseJdbcDatabaseModule { }

Dependency build.gradle.kts:

implementation("ru.tinkoff.kora:database-liquibase")

Module:

@KoraApp
interface Application : LiquibaseJdbcDatabaseModule

Requires the JDBC module because migrations are executed through DataSource. Applications usually include both modules: JdbcDatabaseModule creates JdbcDatabase, and LiquibaseJdbcDatabaseModule adds the migration interceptor.

Configuration

Example of the complete configuration described by the LiquibaseConfig class:

liquibase {
    changelog = "db/changelog/db.changelog-master.xml" //(1)!
}
  1. Path to the main changelog file with migration definitions (default: db/changelog/db.changelog-master.xml).
liquibase:
  changelog: "db/changelog/db.changelog-master.xml" #(1)!
  1. Path to the main changelog file with migration definitions (default: db/changelog/db.changelog-master.xml).

Unlike Flyway, the Liquibase module does not have an enabled setting: if the module is connected to the application graph, migrations run during JdbcDatabase initialization. If a Liquibase migration fails, the module wraps the error in IllegalStateException, and application startup is interrupted.

Migration Files

By default, Liquibase looks for the main changelog file at src/main/resources/db/changelog/db.changelog-master.xml. Liquibase supports different changelog formats, but an SQL-oriented project often benefits from keeping migrations as formatted SQL. The main file can include such migrations with include.

src/main/resources/db/changelog/
  db.changelog-master.xml
  changes/
    001-init-users.sql

Minimal main changelog:

<databaseChangeLog
    xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
                        http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">

    <include file="db/changelog/changes/001-init-users.sql"/>
</databaseChangeLog>

Example of an included migration in formatted SQL:

--liquibase formatted sql

--changeset app:001-init-users
CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    name TEXT NOT NULL
);

Recommendations

Recommendation

Migration modules are not recommended for running migrations on application startup in horizontally scaled environments where the application runs with multiple replicas. Each replica will try to execute migrations during startup. Also keep in mind that every application restart triggers the migration mechanism again.

In such cases, use the Flyway Gradle Plugin for local development, run Flyway from code after database startup in tests, use a Kubernetes Job for production Kubernetes environments, or run migrations separately from CI.