Most software projects are backed by a database, that's widely accepted. The schema
for this database almost always evolves over time: requirements
change, features are added, and so the application's model of the world must evolve.
When this model evolves, the database's schema must change as well. No one wants
to (or should) connect to their production database and apply changes manually, which is why
we need tools to manage schema changes. Most ORMs have basic support, but eventually
projects tend to outgrow them. This is when projects reach to choose a schema migration tool.
Many such tools exist, and it's hard to know which to choose. My goal in this article
is to present 3 popular choices for migration tools for Go projects to help you make this decision.
By way of introduction (and full disclosure): my name is Pedro Henrique, I'm a software engineer from Brazil,
and I've been a contributing member of the Ent/Atlas community for quite a while. I really love open-source
and think there's room for a diverse range of tools in our ecosystem, so I will do my best to provide
you with an accurate, respectful, and fair comparison of the tools.
golang-migrate - Created: 2014 GitHub Stars: 10.3k
Golang migrate is one of the most famous tools for handling database migrations.
Golang migrate has support for many database drivers and migration sources, it takes a simple
and direct approach for handling database migrations.
Goose - Created: 2012 GitHub Stars: 3.2k
Goose is a solid option when choosing a migration tool. Goose has support for the main
database drivers and one of its main features is support for migrations
written in Go and more control of the migrations application process.
Atlas - Created: 2021 GitHub Stars: 2.1k
Atlas is an open-source schema migration tool that supports a declarative workflow to schema
migrations, making it a kind of "Terraform for databases". With Atlas, users can declare their desired schema
and let Atlas automatically plan the migrations for them. In addition, Atlas supports classic versioned migration
workflows, migration linting, and has a GitHub Actions integration.
Golang migrate
Golang migrate was initially created by Matt Kadenbach. In 2018 the project was handed
over to Dale Hui, and today the project resides
on the golang-migrate
organization and is actively maintained, having 202 contributors.
One of Golang migrate's main strengths is the support for various database drivers. If
your project uses a database driver that is not very popular, chances are that Golang
migrate has a driver for it. For cases where your database is not supported, Golang migrate has
a simple API for defining new database drivers. Databases supported by Golang migrate
include: PostgreSQL, Redshift, Ql, Cassandra, SQLite, MySQL/MariaDB, Neo4j, MongoDB,
Google Cloud Spanner, and more.
Another feature of Golang migrate is the support for different migrations sources, for cases where your
migration scripts resides on custom locations or even remote servers.
Goose
Goose has a similar approach to Golang migrate. The project was initially created by Liam
Staskawicz in 2012,
and in 2016 Pressly created a fork improving the usage by adding support for
migrations in Go, handling cases of migrations out of order and custom schemas for migration
versioning. Today Goose has 80 contributors.
Goose only provides support for 7 database drivers, so if your project uses one of the main
databases in the market, Goose should be a good fit. For migration sources, Goose allows only
the filesystem, it's worth pointing out that with Go embed it is possible to embed the migration
files on a custom binary. Goose's main difference from Golang migrate is the support for
migrations written in Go, for cases where it is necessary to query the database during the
migration. Goose allows for different types of migration versioning schemas, improving
one key issue with Golang migrate.
Atlas
Atlas takes a completely different approach to Golang migrate or Goose. While both
tools only focus on proving means of running and maintaining the migration directory, Atlas
takes one step further and actually constructs a graph representing the different database
entities from the migration directory contents, allowing for more complex
scenarios and providing safety for migration operations.
Migrations in Atlas can be defined in two ways:
- Versioned migrations are the classical style, where the migration contents are written by the developer
using the database language.
- Declarative migrations are more similar to Infrastructure-as-Code,
where the schema is defined in a Terraform-like language and the migrations commands are
calculated based on the current and desired state of the database. It's possible to use Atlas in
a hybrid way as well, combining both styles, called Versioned Migration Authoring
where the schema is defined in the Atlas language, but the Atlas engine is used to generate versioned migrations.
On top of Atlas's ability to load the migration directory as a graph of database entities,
an entire infrastructure of static code analysis was built to provide warnings
about dangerous or inefficient operations. This technique is called migration linting and can be
integrated with the Atlas GitHub Action during CI.
In addition, if you would like to run your migrations using Terraform, Atlas has a Terraform provider
as well.
Another key point that Atlas solves is handling migration integrity, which becomes a huge problem when working with
multiple branches that all make schema changes. Atlas solves this problem by using an Integrity
file. While we are on the topic of integrity,
one key feature of Atlas is the support for running the migrations inside a transaction, unlike Goose
during the process of migration. Atlas acquires a lock ensuring that only one migration happens at a time
and the migration order/integrity is respected. For cases where problems are found, Atlas makes
the troubleshooting process easier, allowing schema inspections, dry runs and providing helpful links to
the common problems and solutions.
Feature comparison
- 1: Atlas provides a few packages related to database operations, but the use is limited to complex cases
and there is no package that provides migration usage out of the box.
Wrapping up
In this post we saw different strengths of each migration tool. We saw how Golang
migrate has a great variety of database drivers and database sources, how Goose allows use
to written migration in Go for the complexes migration scenarios and how Atlas makes the
migration a complete different business, improving the safety of the migration operations and
bringing concepts from others fields.