- Different migration requirements
- Simplest migration may be coupled directly as migration with the application (small apps)
- Migrations may be decoupled from the app release/deployment - Prestaging migrations prior to release - Migration cannot be done eagerly in deployment, but should be run as on-platform task - Migrations may be done manually, running migration tools directly against database - Complex migrations may require ETL, Map/Reduce, or Event streaming through data pumps
- We will cover eager migrations, and simple on-platform task migrations with flyway
In simplest example, simply including Flyway dependency in the application's dependencies will autoconfigure flyway as initial Springboot datasource migration. The database migration scripts must also be included in the classpath (resources)
-
Add flyway-core dependency to project dependencies
compile('org.flywaydb:flyway-core:5.0.7')
-
Add flyway migration scripts to the resource db/migrations path
-
Configure flyway - we will assume baseline on migration (automatically set the baseline to 1st version if no baseline already exists)
-
Start the application, verify flyway migration is complete.
That's it!
In large or complex applications, or in applications with large or complex databases, it may be necessary to stage migrations separately from, or ahead of, application deployments.
In this case we can easily run database migrations as one-off tasks, but on same PaaS platform where running the applications.
Spring Cloud Tasks are a good candidate for application level tracking of migration tasks for audit trail and monitoring.
On Cloud Foundry we may use Spring Cloud Tasks in combination with Cloud Foundry Tasks, or, on Pivotal Cloud Foundry we may use the PCF Scheduler to manage and track Jobs.
In this lab we will choose to implement Spring Cloud Tasks with PCF Scheduled Jobs.
-
Generate Springboot Application
@SpringBootApplication public class FlywayApplication { private Logger logger = LoggerFactory.getLogger(this.getClass()); public static void main(String[] args) { SpringApplication.run(FlywayApplication.class, args); } }
-
Add following dependencies: - Actuator - Spring Boot Web Starter - Spring Boot JDBC Starter - Spring Cloud Task Starter - Flyway Core - JDBC Driver for MySQL
dependencies { compile 'org.springframework.cloud:spring-cloud-starter-task:1.2.2.RELEASE' compile('org.springframework.boot:spring-boot-starter-jdbc:1.5.10.RELEASE') compile('org.springframework.boot:spring-boot-starter-web:1.5.10.RELEASE') compile('org.springframework.boot:spring-boot-starter-actuator:1.5.10.RELEASE') compile('mysql:mysql-connector-java:6.0.6') compile('org.flywaydb:flyway-core:5.0.7') testCompile('org.springframework.boot:spring-boot-starter-test') }
By default Flyway migration strategy is eager -- it will run the migration during Springboot datasource init by default.
We do not want to eager migrate, we only want to stage the migration files as part of application classpath, and monitor through actuator.
To disable migration, we can override the Flyway migration strategy to turn the migrate into a Noop, but
still retain staging, and actuator monitoring, but providing an alternate Spring Bean implementation of FlywayMigrationStrategy
. We will use a property migrate.command
as a condition to override the
FlywayMigrationStrategy:
@Component
@ConditionalOnExpression("'${migrate.command}'.equals('stage')")
public class StageMigrationStrategy implements FlywayMigrationStrategy {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public void migrate(Flyway flyway) {
logger.info("Migration staged");
}
}
But we also want to be able to leverage the same application to execute tasks, in separate process, separate container.
We will use Spring Cloud Tasks to do so with Command Line Runner in Spring Application. Use a boolean Conditional Property
called migrate.task
to enable or disable task mode:
@Configuration
@EnableTask
@ConditionalOnProperty("migrate.task")
public class FlywayMigrationTaskConfig {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Bean
public CommandLineRunner runner() {
return new CommandLineRunner() {
@Override
public void run(String... args) throws Exception {
// TODO Failure not handled, need to explicitly handle JDBC Migration Task here
logger.info("Completed flyway migration task");
}
};
}
}
We need to set up the application property defaults:
flyway:
enabled: true
validate-on-migrate: true
baseline-on-migrate: true
migrate:
task: false
command: stage # stage, migrate options
endpoints:
actuator:
enabled: true
flyway:
enabled: true
management:
security:
enabled: false
-
Build and Start the FlywayApplication in staging/monitoring mode:
./gradlew clean build ./gradlew bootRun
-
Post the Actuator flyway endpoint, what do you see?
https://localhost:8080/flyway
-
You should see 3 pending migrations
-
Shutdown the FlywayApplication
-
Start the FlywayApplication in migrate task mode:
MIGRATE_TASK=true MIGRATE_COMMAND=migrate ./gradlew bootRun
-
What happens? Once the task is complete, the FlywayApplication terminates.
-
Log into database, select from TASKS table. This is default Spring Cloud Task mechanism for migration job audit trail
-
Start the FlywayApplication in staging/monitoring mode:
./gradlew bootRun
-
Post the Actuator flyway endpoint, what do you see?
https://localhost:8080/flyway
-
You should see 3 successful migrations
-
Shutdown the FlywayApplication
-
Set up the manifest file for the FlywayApplication. Its default environment will launch in staging and monitoring mode.
--- applications: - name: person-migration memory: 768M instances: 1 path: ./build/libs/bootified-flyway-migration-0.0.1-SNAPSHOT.jar routes: - route: person-migration-{your initials}.cfapps.io buildpack: java_buildpack services: - person-db-service - job-scheduler
-
Create the database service if not already created. This will be the database that is bound to flyway config deployed in FlywayApplication through the Spring Cloud Foundry Connector, no endpoint configuration is required:
cf create-service cleardb spark person-db-service
-
Create the job-scheduler service:
cf create-service scheduler-for-pcf standard job-scheduler
-
Download and install the cf job scheduler plugins
-
Push the FlywayApplication. This will stage and set up actuator monitor:
cf push
-
Once push is complete, create job for migration. You will need to copy the run command from output of cf push, and add environment variables to enable task mode, and migrate command. It should look something like this:
cf create-job person-migration-bkable migrate-person 'MIGRATE_TASK=true MIGRATE_COMMAND=migrate JAVA_OPTS="-agentpath:$PWD/.java-buildpack/open_jdk_jre/bin/jvmkill-1.10.0_RELEASE=printHeapHistogram=1 -Djava.io.tmpdir=$TMPDIR -Djava.ext.dirs=$PWD/.java-buildpack/container_security_provider:$PWD/.java-buildpack/open_jdk_jre/lib/ext -Djava.security.properties=$PWD/.java-buildpack/security_providers/java.security $JAVA_OPTS" && CALCULATED_MEMORY=$($PWD/.java-buildpack/open_jdk_jre/bin/java-buildpack-memory-calculator-3.9.0_RELEASE -totMemory=$MEMORY_LIMIT -stackThreads=300 -loadedClasses=14717 -poolType=metaspace -vmOptions="$JAVA_OPTS") && echo JVM Memory Configuration: $CALCULATED_MEMORY && JAVA_OPTS="$JAVA_OPTS $CALCULATED_MEMORY" && SERVER_PORT=$PORT eval exec $PWD/.java-buildpack/open_jdk_jre/bin/java $JAVA_OPTS -cp $PWD/. org.springframework.boot.loader.JarLauncher'
-
Post actuator endpoint for your FlywayApplication:
https://person-migration-{your initials}.cfapps.io/flyway
-
You should see 3 pending (staged) migrations
-
Given we are using the PCF Scheduler, we should schedule the job. For the lab we will kick off manually:
cf run-job migrate-person
-
You can view for successful completion through job history:
cf job-history migrate-person
-
Post actuator endpoint for your FlywayApplication:
https://person-migration-{your initials}.cfapps.io/flyway
-
You should see 3 successful migrations
- What happens if a migration fails? - If it fails mid-flight without DDL transactions, may require rollback of database, or compensating undo. Flyway Pro provides official Undo support; however, the same may be another versioned migration in the community addition. - If migration fails due to SQL syntax, the migration may be repaired through Flyway API, and the migration re-run.
- What if we need to clean a non-prod database? - Flyway supports clean option through configuration, or API. It must be disabled for production
- Can we validate migrations, without running it?
- Yes, the
validate-on-migrate
option does this, but validate API may be invoked following migration
- Extend the FlywayApplication with the following: - Ability to run a task to repair migration - Ability to run a task to clean the database - Ability to run a task to validate migrations