
https://github.com/geirolz/fly4s
The most famous library to handle database migrations in Java is for sure Flyway.
It works very well and the community edition has a lot of features as well.
Problem
Flyway APIs are written in the standard OOP paradigm, so throwing exceptions, manually managing resources, etc...
Solution
Fly4s
is a lightweight, simple and functional wrapper for Flyway.
The aim ofFly4s
is straightforward, wrapping theFlyway
APIs in order to guarantee referential transparency, pureness, resource handling and type safety. To achieve this goal,Fly4s
use the typelevel librariescats
andcats-effect
.
Getting started
The first step, import theFly4s
library in our SBT project.
So, add the dependency in yourbuild.sbt
file.
Fly4s depends on Flyway, so we'll have access to Flyway as well
(check the GitHub repo for the latest version)
libraryDependencies += "com.github.geirolz" %% "fly4s-core" % "0.0.4"
Migrations files
As the plain Flyway, we have to create a folder that will contain our migrations scripts, often inresources/db
.
In this folder, we have to put all our migration. We can have:
For this example, we are going to use a simplebaseline migration
to add a table to our database schema.
Baseline migrations are versioned and executed only when needed. The version is retrieved from the script file name.
So in this case,V001__create_user_table.sql
, the version will be001
(remember the double underscore afterV
).
Here we have our first migration(for MySQL database)
resources/db/V001__create_user_table.sql
CREATETABLE`user`(`id`bigint(20)NOTNULLAUTO_INCREMENTPRIMARYKEY,`name`varchar(30)NOTNULL,`surname`varchar(30)NOTNULL);
Defining database configuration (optional)
A good practice is to create a case class to handle the database configuration(this combined with PureConfig or other config libraries makes your app very robust from the configuration point of view)
Let's create a simple case class to achieve this.
caseclassDatabaseConfig(url:String,user:Option[String],password:Option[Array[Char]],migrationsTable:String,migrationsLocations:List[String])
N.B. apart from the common fields suchurl
,user
andpassword
we'll use:migrationsTable
to define the Flyway table name(used to store the migration status) andmigrationsLocations
to specify a list of the folders that contain our migration scripts.
Instantiating Fly4s
Ok so, now we have all our migration scripts in our folder(resources/db
), we haveFly4s
as a dependency of our project, and we have a case class that will contain the database configuration.
To instantiateFly4s
we can usemake
to create a new DataSource(under the hood) starting from the parameters ormakeFor
in order to create it for an already existentDataSource
(for example from Doobie HikariDataSource).make
andmakeFor
method returns aResource
type class that when released/interrupted safely close theDataSource
connection.
In bothmake
andmakeFor
methods, we can specify the parameterconfig
.Fly4sConfig
is a trivial wrapper for flywayConfiguration
but instead of having a builder, we have a case class.
valdbConfig:DatabaseConfig=???valfly4sRes:Resource[IO,Fly4s]=Fly4s.make[IO](url=dbConfig.url,user=dbConfig.user,password=dbConfig.password,config=Fly4sConfig(table=dbConfig.migrationsTable,locations=Location.of(dbConfig.migrationsLocations)))
Using Fly4s
Ok, we have done with the configuration!
We are ready to migrate our database schema with the power of Flyway and the safety of Functional Programming!
We can useuse
orevalMap
fromResource
to safely access to the Fly4s instance. In case we have multipleResource
s in our application probablyevalMap
allow us to better combine them using and release them all together at the same time.
We can create a simple utility method to do this
privatedefmigrateDb(dbConfig:DatabaseConfig):Resource[IO,Unit]=Fly4s.make[IO](url=dbConfig.url,user=dbConfig.user,password=dbConfig.password,config=Fly4sConfig(table=dbConfig.migrationsTable,locations=Location.of(dbConfig.migrationsLocations))).evalMap(fly4s=>for{_<-logger.debug(s"Applying migration for ${dbConfig.name}")migrationResult<-fly4s.validateAndMigrate[IO].result_<-logger.info(s" Applied ${migrationResult.migrationsExecuted} migrations to ${dbConfig.name} database")}yield())
Conclusions
We have done it! So, to recap, we have:
- Created a folder under
resources
to put our migrations(db
) - Imported
Fly4s
as a dependency in our project - Created a configuration case class to describe our database configuration
- Instantiated a
Fly4s
instance creating a newDataSource
- Migrated our database using
validateAndMigrate
- At the application shutdown/interruption
Resource
(from cats-effect) will safely release theDataSource
With a few lines, we have migrated our database safely handling the connection and the configuration.
As flyway, Fly4s provides multiple methods such as:
- validateAndMigrate
- migrate
- undo
- validate
- clean
- info
- baseline
- repair
Useful links
https://github.com/geirolz/fly4s
https://flywaydb.org/documentation
https://typelevel.org/cats/
https://typelevel.org/cats-effect/
https://pureconfig.github.io/
Top comments(0)
For further actions, you may consider blocking this person and/orreporting abuse