-
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
198 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,198 @@ | ||
# Fly4s | ||
[![Build Status](https://github.com/geirolz/fly4s/actions/workflows/cicd.yml/badge.svg)](https://github.com/geirolz/fly4s/actions) | ||
[![codecov](https://img.shields.io/codecov/c/github/geirolz/fly4s)](https://codecov.io/gh/geirolz/fly4s) | ||
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/db3274b55e0c4031803afb45f58d4413)](https://www.codacy.com/manual/david.geirola/fly4s?utm_source=github.com&utm_medium=referral&utm_content=geirolz/fly4s&utm_campaign=Badge_Grade) | ||
[![Sonatype Nexus (Releases)](https://img.shields.io/nexus/r/com.github.geirolz/fly4s_2.13?server=https%3A%2F%2Foss.sonatype.org)](https://mvnrepository.com/artifact/com.github.geirolz/fly4s) | ||
[![Scala Steward badge](https://img.shields.io/badge/Scala_Steward-helping-blue.svg?style=flat&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAQCAMAAAARSr4IAAAAVFBMVEUAAACHjojlOy5NWlrKzcYRKjGFjIbp293YycuLa3pYY2LSqql4f3pCUFTgSjNodYRmcXUsPD/NTTbjRS+2jomhgnzNc223cGvZS0HaSD0XLjbaSjElhIr+AAAAAXRSTlMAQObYZgAAAHlJREFUCNdNyosOwyAIhWHAQS1Vt7a77/3fcxxdmv0xwmckutAR1nkm4ggbyEcg/wWmlGLDAA3oL50xi6fk5ffZ3E2E3QfZDCcCN2YtbEWZt+Drc6u6rlqv7Uk0LdKqqr5rk2UCRXOk0vmQKGfc94nOJyQjouF9H/wCc9gECEYfONoAAAAASUVORK5CYII=)](https://scala-steward.org) | ||
[![Mergify Status](https://img.shields.io/endpoint.svg?url=https://api.mergify.com/v1/badges/geirolz/fly4s&style=flat)](https://mergify.io) | ||
[![GitHub license](https://img.shields.io/github/license/geirolz/fly4s)](https://github.com/geirolz/fly4s/blob/main/LICENSE) | ||
|
||
<div align="center"> | ||
<img src="images/logo.png" alt="logo" width="50%"/> | ||
</div> | ||
|
||
A lightweight, simple and functional wrapper for Flyway using cats-effect. | ||
|
||
### Compatibility matrix | ||
|
||
| **Fly4s** | **Flyway** | **Branch** | | ||
|:--------------------------------------------------------------------:|:----------:|:--------------------------------------------------------:| | ||
| [0.x](https://github.com/geirolz/fly4s/releases?q=0.&expanded=false) | 9.x | [0.x-9.x](https://github.com/geirolz/fly4s/tree/0.x-9.x) | | ||
| [1.x](https://github.com/geirolz/fly4s/releases?q=1.&expanded=false) | 10.x | [main](https://github.com/geirolz/fly4s/tree/main) | | ||
|
||
|
||
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. | ||
But Flyway APIs are written in the standard OOP paradigm, so throwing exceptions, manually managing resources, etc... | ||
|
||
`Fly4s` is a lightweight, simple and functional wrapper for Flyway. | ||
The aim of `Fly4s` is straightforward, wrapping the `Flyway` APIs to guarantee | ||
referential transparency, pureness, resource handling and type safety. | ||
To achieve this goal, `Fly4s` use the typelevel libraries `cats` and `cats-effect`. | ||
|
||
- [Getting started](#getting-started) | ||
- [Migrations files](#migrations-files) | ||
- [Defining database configuration](#defining-database-configuration) | ||
- [Instantiating Fly4s](#instantiating-fly4s) | ||
- [Using Fly4s](#using-fly4s) | ||
- [Conclusions](#conclusions) | ||
- [Useful links](#useful-links) | ||
--- | ||
|
||
### Adopters | ||
If you are using Fly4s in your company, please let me know and I'll add it to the list! It means a lot to me. | ||
|
||
### Getting started | ||
Fly4s supports Scala 2.13 and 3. | ||
The first step, import the `Fly4s` library in our SBT project. | ||
So, add the dependency in your `build.sbt` file. | ||
Fly4s depends on Flyway, so we'll have access to Flyway as well | ||
|
||
```sbt | ||
libraryDependencies += "com.github.geirolz" %% "fly4s" % "1.0.0" | ||
``` | ||
|
||
### Migrations files | ||
As the plain Flyway, we have to create a folder that will contain our migrations scripts, often in `resources/db`. | ||
|
||
In this folder, we have to put all our migration. We can have: | ||
- [Baseline migrations](https://flywaydb.org/documentation/tutorials/baselineMigrations) | ||
- [Repeatable migrations](https://flywaydb.org/documentation/tutorials/repeatable) | ||
- [Undo migrations](https://flywaydb.org/documentation/tutorials/undo) | ||
|
||
For this example, we are going to use a simple `baseline 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 be `001`(remember the double underscore after `V`). | ||
|
||
Here we have our first migration(for MySQL database) | ||
|
||
`resources/db/V001__create_user_table.sql` | ||
```sql | ||
CREATE TABLE `user` ( | ||
`id` bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY, | ||
`name` varchar(30) NOT NULL, | ||
`surname` varchar(30) NOT NULL | ||
); | ||
``` | ||
|
||
### Defining database configuration | ||
A good practice is to create a case class to handle the database configuration(this combined with PureConfig | ||
or others | ||
config libraries make your app very robust from the configuration point of view) | ||
|
||
Let's create a simple case class to achieve this. | ||
```scala | ||
case class DatabaseConfig( | ||
url: String, | ||
user: Option[String], | ||
password: Option[Array[Char]], | ||
migrationsTable: String, | ||
migrationsLocations: List[String] | ||
) | ||
``` | ||
|
||
N.B. apart from the common fields such `url`, `user` and `password` we'll use: `migrationsTable` to define the | ||
Flyway table name(used to store the migration status) and `migrationsLocations` 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 have `Fly4s` as a dependency | ||
of our project, | ||
and we have a case class that will contain the database configuration. | ||
|
||
To instantiate `Fly4s` we can use `make` to create a new DataSource(under the hood) starting from the parameters | ||
or `makeFor` in order to create it for an already existent `DataSource`(for example from Doobie HikariDataSource). | ||
`make` and `makeFor` method returns a [`Resource`](https://typelevel.org/cats-effect/docs/std/resource) type class | ||
that when released/interrupted safely close the `DataSource` connection. | ||
|
||
In both `make` and `makeFor` methods, we can specify the parameter `config`. `Fly4sConfig` is a trivial wrapper for | ||
flyway `Configuration` but instead of having a builder we have a case class. | ||
|
||
```scala | ||
import fly4s.* | ||
import fly4s.data.* | ||
import cats.effect.* | ||
|
||
val dbConfig: DatabaseConfig = DatabaseConfig( | ||
url = "url", | ||
user = Some("user"), | ||
password = None, | ||
migrationsTable = "flyway", | ||
migrationsLocations = List("db") | ||
) | ||
// dbConfig: DatabaseConfig = DatabaseConfig( | ||
// url = "url", | ||
// user = Some(value = "user"), | ||
// password = None, | ||
// migrationsTable = "flyway", | ||
// migrationsLocations = List("db") | ||
// ) | ||
|
||
val fly4sRes: Resource[IO, Fly4s[IO]] = Fly4s.make[IO]( | ||
url = dbConfig.url, | ||
user = dbConfig.user, | ||
password = dbConfig.password, | ||
config = Fly4sConfig( | ||
table = dbConfig.migrationsTable, | ||
locations = Locations(dbConfig.migrationsLocations) | ||
) | ||
) | ||
// fly4sRes: Resource[IO, Fly4s[IO]] = Allocate( | ||
// resource = cats.effect.kernel.Resource$$$Lambda$27110/0x00000008055d1368@511cd40c | ||
// ) | ||
``` | ||
|
||
### 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 use `use` or `evalMap` from `Resource` to safely access to the Fly4s instance. In case we have | ||
multiple `Resource`s in our application probably `evalMap` allow us to better combine them using and releasing | ||
them all together at the same time. | ||
|
||
We can create a simple util method to do this | ||
|
||
```scala | ||
import fly4s.implicits.* | ||
|
||
def migrateDb(dbConfig: DatabaseConfig): Resource[IO, MigrateResult] = | ||
Fly4s.make[IO]( | ||
url = dbConfig.url, | ||
user = dbConfig.user, | ||
password = dbConfig.password, | ||
config = Fly4sConfig( | ||
table = dbConfig.migrationsTable, | ||
locations = Locations(dbConfig.migrationsLocations) | ||
) | ||
).evalMap(_.validateAndMigrate.result) | ||
``` | ||
|
||
### Conclusions | ||
We have done it! So, to recap, we have: | ||
1. Created a folder under `resources` to put our migrations(`db`) | ||
2. Imported `Fly4s` as a dependency in our project | ||
3. Created a configuration case class to describe our database configuration | ||
4. Instantiated a `Fly4s` instance creating a new `DataSource` | ||
5. Migrated our database using `validateAndMigrate` | ||
6. At the application shutdown/interruption `Resource`(from cats-effect) will safely release the `DataSource` | ||
|
||
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://flywaydb.org/documentation | ||
- https://typelevel.org/cats/ | ||
- https://typelevel.org/cats-effect/ | ||
- https://pureconfig.github.io/ |