MigratiON manAgeR with a bad aCronym, duH
Over the last several projects I've worked, one of the main problems I've encountered is managing databases throughout different environments. There are several migration managers out there, but all of them are overly bloated as most of them rely on ORMs, and were an overkill for what I really needed:
Hence, Monarch was created.
.env
file).1_first_migrations.sql
). This poses a problem
when submitting the migrations to version control and make said
migrations in a collaborative environment. The way Monarch works,
dependent migrations are not handled as a single stream, but rather
as branching succesions. This means that two developers can work
simultaneously on changing different tables at the same time
without stepping on each others way, provided said changes are not
related to each other.1_create_person.sql
, 2_change_address.sql
,
3_change_person.sql
. If you wanted to only make changes to the Person
table but preserve the state of Address, you'd have to cherry pick the
needed migrations in <insert name of migration tool>, while in
Monarch, you could only run the last Person-related migration and the
program will apply only the strictly needed migration to make that
work.If any of this sounds appealing, maybe give Monarch a try.
# Download the binary
$ sudo curl -fsSL -o /usr/local/bin/monarch https://github.com/aropie/monarch/releases/download/v1.0/monarch
# Make it executable
$ sudo chmod +x /usr/local/bin/monarch
Not available yet :(
Migrations are single files that represent an atomic change to the DB's state.
Migrations are always additive. This means that once a migration is commited into Monarch, it shouldn't be modified, and changes can only be reverted through a new migration.
Migrations can't contain BEGIN TRANSACTION/COMMIT statements. Monarch takes care of this and all the migrations are run as a single transaction.
Dependencies must be declared at the header. If a migration is not self-contained, i.e. it assumes a pre-existing state of the DB, all dependencies must be declared at the header. More on this header later.
Migrations are looked for in the migrations dir, migrations
by default.
This can be changed with the -d
flag.
# Run a single migration
$ ./monarch -m some-pending-migrations.sql
# Or run all available migrations
$ ./monarch -a
It's important to note that Monarch runs all the migrations requested (be it a single migration
and its dependencies or a whole schema) as a single transaction. This means that if you are applying
migrations 1 through 5 and no. 3 breaks the DB state, all changes are rolled back and none of the 5 migrations
are applied. To override this behavior, run the migration with -t
. With the -t
flag, if migration
3 breaks the DB state, migrations 1 and 2 will already be applied and saved.
Monarch saves the current DB state through a migrations
table, in order to know which migrations
are needed to move from one state to another. You can make Monarch ignore
previously applied migrations with -ignore-applied
, and avoid new migrations being registered
into the DB with -skip-register
, or only register migrations without actually applying them with
--fake
.
Monarch functions on two different databases: an internal one to save the applied migrations, and a target database, which is the one where the migrations will actually be applied.
One of the objectives of Monarch is to be database-agnostic. Internally, Monarch uses SQLAlchemy to handle the connections with the databases. This means that all the databases supported by SQLAlchemy are supported by Monarch as well, which covers all of the most common DB choices (MySQL, PostgreSQL, SQLite, Microsoft SQL Server).
To configure the database connections, a database url
is used,which is defined through environment variables.
Simply set INTERNAL_DB_URL
and TARGET_DB_URL
and you're ready to go:
# Both of these could point to the same db.
$ export INTERNAL_DB_URL='sqlite:///internal.db'
$ export TARGET_DB_URL='postgresql://postgres:@localhost'
To allow for different development environments, a .env
can also be supplied to set
the corresponding environment variables.
The header is where Monarch figures out how to handle the migration. A valid Monarch header
starts with --!
and then the declaration which is json-formatted in the following manner:
{command1: value, command2: value, ...}
.
The existing commands are all that follow:
And that's all.
--! {"depends_on": ["start_up_db.sql", "delete_a_table.sql"]}
CREATE TABLE person(
id INTEGER,
name VARCHAR(80),
PRIMARY KEY(id)
);
Monarch solves the declared dependencies by creating a dependency tree and follow it
recursively. Suppose you have first_migration.sql
and another migration
second_migration.sql
which depends on first_migration.sql
. The header for second_migration.sql
would look something like:
--! {"depends_on": ["first_migration.sql"]}
If later you have a third migration my_third_migration.sql
which depends on second_migration.sql
(and therefore also on first_migration.sql
) its header would look like:
--! {"depends_on": ["second_migration.sql"]}
Note that we're not declaring first_migration.sql
as a dependency here because second_migration.sql
already has it listed, so Monarch will traverse the dependency tree and apply all necessary migrations
when migrating third_migration.sql
.