Keeping Track of Database Schema Changes Using Go

Tips to help you with database changes

Hernan Reyes
Better Programming

--

Migrations help you build your database schema
Migrations help you build your database schema

Overview

Have you ever wondered how developers handle changes in the database schema of a project? What do they do when a new table/column/index is added, or when a table, column, or index is dropped.

How do they ensure everyone involved in the project has all the changes applied to their database? Well, for this, they keep track of a history of every change made to the database schema.

SQL migrations ad GIT agreement
SQL migrations agreement with a version control system

This was something I didn’t know until I first worked with other people and I read the README of the project and said that I have to run the “migrations” to have my database schema up-to-date.

That made me ask myself: how didn’t it cross my mind to handle something that obvious before? Well, it was because I never change off the computer or reformatted my PC, so it made me reinstall the whole project from zero to realize that I didn’t have a way to restore my database without a backup.

My goal in this article is to show you how this database migration works so you know what to do when new changes are applied to the database, whether you’re working solo or with other people.

How it Works

The way it works is simple, you have to do the same thing you do when you keep a history of the project’s code. So, we’ll need a version control system like GIT. There, we can ensure everyone involved in the project can have the database’s changes history up to date.

Here, I’ll show you two ways to handle the database migrations, but first, let’s see what they have in common:

You’ll write files that contain the SQL with a name format like: {version}_{description}.{up/down}.{ext}

The description can be a short explanation of what the migration file does, for example:

  1. create_users_table
  2. adds_birthdate_column_to_users_table
  3. drops_country_column_from_users_table

The SQL to apply changes to the schema (known as up migration) and a SQL to reverse those changes (known as down migration)

That’s why in the previous point, the name format indicates whether is an up or down migration

Note: Some libraries manage one file and with comments indicate the up SQL and down SQL

A way to run the migrations in order

For this, we make use of the version of the filename format, you can put a sequence number or the timestamp of when you created the file, so when we run the migrations we execute them in ascending order. This is needed because you may add migrations that depend on other migrations to be executed first.

Ensure we don’t run a migration twice

Libraries may create a table to keep a log of the executed migrations, so they don’t do it twice.

The extension of the file depends on the method you choose to handle the migrations

It can be a .sql file or the extension of your programming language

Now that we know what they have in common, let’s see each method in action.

Pure SQL’s Migrations

Here you’ll have to create a directory to store the .sql files, which will contain the changes made to the database schema and a way to reverse them. Following the format described in the previous section, you’ll have files like these:

SQL migrations directory
SQL migrations directory tree

Then in your migration files, just write the changes you want to make. For example, the 000001_create_users_table.up.sql migration in the above image would have something like this:

and for the migration 000001_create_users_table.down.sql, the needed SQL to reverse the changes made by the up migration file:

DROP TABLE IF EXISTS users;

Note: Some libraries can read the SQL files from sources like GitHub, GitLab, AWS s3, etc.

For this method, you can use the next libraries:

  1. https://github.com/golang-migrate/migrate
  2. https://github.com/pressly/goose
  3. https://www.prisma.io/migrate
  4. https://pypi.org/project/yoyo-migrations/
  5. https://github.com/alexyslozada/migrations

Migrations With Your Programming Language

Here applied the same conventions as in the Pure SQL’s migrations, but instead of having .sql migrations, you’ll have migrations with the extension of your programming language, in my case, I’ll be using Golang, so the previous structure of files I showed you earlier will look like this with a timestamp versioning instead of a sequence:

Go SQL migrations directory

As you can see, unlike the previous method, here we only have three files. Why is that? It is because of the library I’m using. We indicate the up and down migration in the same file. Here is the content for the users table:

Here you have an up and down function which is passed to goose.AddMigration(). This will obviously be different depending on your programming language and the library you’re using.

For this method, you can use the next libraries:

  1. https://github.com/pressly/goose
  2. https://laravel.com/docs/9.x/migrations
  3. https://pypi.org/project/yoyo-migrations/

What if I’m Mid-Project and Don’t Have Migrations?

For this, you can create a backup of the database schema, so you can add it as an initial migration, but you have to make sure that the library you use does not execute it in your local database (because you already have those changes), so you have to dig into the library to see how they keep track of the migrations that have been already run, the ones I know creates a table in your database, so you can start there.

Then, you can start keeping track of the database changes. If you want a new table, add a migration. If you want to drop a column? Add a migration. If you want an index? Add a migration. Remember, every change to your database schema must have a migration.

Conclusion

Here I gave you an overview of database migrations, with two common approaches on how to handle them. Personally, I like to use the first method because I just have to worry about writing the SQL and not writing code to execute the migration with my programming language. I’ve been using this method for over three years now and never had the need to use the second one.

I hope you’ve learned something new today and started using these migrations if you’re not already. Three years ago I didn’t know how to use them, but with practice, you will be prepared to use them or teach them to other developers.

If you want to know how to create and run the file migrations automatically, refer to the libraries I mentioned. There they explain how to use them.

Thank you for reading and see you in the next article.

--

--

Hello, I’m a Backend dev currently working remotely and sharing my knowledge through articles and twitch if you speak spanish https://www.twitch.tv/hernanreyes_