PPL 2020 — Document Builder: Database Migration dengan Sequelize
Proses membangun sebuah aplikasi merupakan proses yang tidak akan ada hentinya, kecuali Anda menghentikannya sendiri. Seiring waktu berjalan, Anda pasti akan melakukan pembaharuan terhadap aplikasi Anda, baik itu penambahan fitur maupun melakukan bugfix. Apabila Anda menggunakan DBMS (MySQL, PostgreSQL, dll) pada aplikasi Anda, maka tentu Anda harus update database Anda setiap ada fitur baru yang membutuhkan database.
Bayangkan Anda adalah seorang DevOps/Sysadmin yang menangani deployment dari aplikasi Anda. Setiap kali ada update database, Anda harus melakukan update tersebut secara manual. Hal ini tentu tidak efektif apabila deployment sangat sering dilakukan, dan mempunyai potensi human error, belum lagi apabila dibutuhkan rollback karena suatu kesalahan.
Proses update tersebut seringkali dikatakan sebagai database migration. Sekarang, banyak sekali tool yang bisa membantu Anda untuk melakukan migration secara incremental (bertahap), reversible (bisa di-rollback) dan otomatis sehingga Anda bisa duduk santai tanpa melakukan update manual pada saat deployment. Terdapat tool khusus untuk migration seperti Flyway dan Liquibase, atau tool yang muncul sebagai tambahan dari library ORM, seperti Sequelize. Masing-masing tool memiliki kelebihan dan kekurangannya masing-masing. Pada Document Builder, kami menggunakan Sequelize untuk migration.
Migration dengan Sequelize
Untuk menggunakan migration dengan Sequelize, Anda setidaknya harus memiliki Sequelize dan Sequelize CLI. Sequelize CLI sendiri berguna untuk melakukan project bootstrapping dan menjalankan migration. Apabila Anda belum pernah menggunakan Sequelize, jalankan perintah berikut untuk melakukan setup pertama kali:
npx sequelize init
Ubah konfigurasi koneksi database anda pada config/config.json
ke database yang Anda gunakan. Sebagai contoh, saya menggunakan koneksi MySQL (perlu menambahkan package mysql2) ke localhost:
{
"development": {
"username": "root",
"password": "password",
"database": "database_development",
"host": "127.0.0.1",
"dialect": "mysql",
"operatorsAliases": false
},
"test": {
"username": "root",
"password": "password",
"database": "database_test",
"host": "127.0.0.1",
"dialect": "mysql",
"operatorsAliases": false
},
"production": {
"username": "root",
"password": "password",
"database": "database_production",
"host": "127.0.0.1",
"dialect": "mysql",
"operatorsAliases": false
}
}
Apabila Anda ingin melakukan perintah Sequelize CLI untuk environment yang berbeda, cukup tambahkan NODE_ENV=nama_env
di sebelum perintah Anda, sebagai contoh untuk membuat database di environment test
:
NODE_ENV=test npx sequelize db:create
Maka akan terbuat database bernama database_test
sesuai dalam config.json
di atas.
Membuat Migration Script
Migration script merupakan script yang akan dijalankan saat tahap migration. Untuk membuat script tersebut, gunakan perintah npx sequelize migration:create --name nama_script
. Sebagai contoh, akan dibuat migration script yang akan menambahkan tabel Todo
yang mewakili satu entry pada to-do list:
npx sequelize migration:create --name create-todo
Sequelize akan membuatkan migration script yang bernama XXXXXXXXXXXX-create-todo.js
. Angka awal merupakan timestamp yang berguna sebagai urutan dalam menjalankan migration script, dan juga untuk menghindari konflik apabila ada orang lain yang membuat migration script yang sama. Di dalam script tersebut berisi:
'use strict';module.exports = {
up: (queryInterface, Sequelize) => {
/*
Add altering commands here.
Return a promise to correctly handle asynchronicity. Example:
return queryInterface.createTable('users', { id: Sequelize.INTEGER });
*/
}, down: (queryInterface, Sequelize) => {
/*
Add reverting commands here.
Return a promise to correctly handle asynchronicity. Example:
return queryInterface.dropTable('users');
*/
}
};
Dalam migration script terdapat dua buah komponen, yaitu up
dan down
. Singkatnya, up
akan dijalankan apabila kita akan menjalankan migration script tersebut (apply migration), dan down
akan dijalankan apabila kita ingin membatalkan migration (undo migration). Maka, perintah yang akan diisi untuk setiap komponen harus berlawanan. Sebagia contoh, apabila perintah up
menambahkan tabel bernama todo
, maka perintah down
, harus menghapus tabel todo
tersebut.
Untuk contoh migration script ini, saya akan menambahkan tabel yang bernama Todos
beserta kolomnya.
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Todos', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING
},
done: {
type: Sequelize.BOOLEAN,
defaultValue: false
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allwNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Todos');
}
};
Sekarang, kita akan jalankan perintah migrate:
npx sequelize db:migrate
Apabila Anda lihat database Anda, maka akan muncul tabel Todos
dan kolom yang sesuai:
Misalkan untuk sekarang, migration script tersebut sudah terdeploy ke staging maupun production. Ternyata, ada update baru pada aplikasi yang menambahkan kolom author
untuk menandakan siapa pembuat to-do tersebut. Anda bisa menambahkan migration script baru yang menambahkan kolom tersebut, misalkan nama script-nya adalah todos-add-author
.
npx sequelize migration:create --name todos-add-author
Kemudian, script-nya adalah sebagai berikut:
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.addColumn('Todos', 'author', {
type: Sequelize.STRING
});
}, down: (queryInterface, Sequelize) => {
return queryInterface.removeColumn('Todos', 'author');
}
};
Kemudian, jalankan migrate kembali:
npx sequelize db:migrate
Sekarang, kolom author
sudah ditambahkan:
Skenario selanjutnya: Ternyata, fitur author yang baru mempunyai bug, dan Anda diharuskan untuk melakukan rollback database ke masa sebelum fitur tersebut. Caranya mudah, cukup lakukan undo:
npx sequelize db:migrate:undo
Sekarang, kolom author tidak ada lagi:
Tambahan: Apabila Anda ingin melakukan undo terhadap semua script, lakukan perintah tersebut (sebelumnya telah dilakukan migration dengan kedua script):
npx sequelize db:migrate:undo:all
Sekarang, tabel Todos
sudah tidak ada lagi:
Tabel SequelizeMeta
merupakan tabel yang dibuat oleh Sequelize untuk mencatat script apa saja yang sudah dijalankan.
Kira-kira begitulah cara menggunakan migration menggunakan Sequelize. Apabila Anda tertarik, Anda dapat membaca lebih lanjut di dokumentasi Sequelize-nya langsung.
Automatic Migration Saat Deployment
Apabila Anda menggunakan CI/CD, maka kita bisa menjalankan migration secara otomatis di server production. Cara ini sangat bergantung dengan arsitektur yang Anda gunakan, tetapi intinya adalah cukup jalankan perintah migrate pada database milik production. Untuk kasus kami, aplikasi kami sudah containerized (menggunakan Docker) dan hanya server aplikasi yang bisa mengakses database. Berikut adalah cara yang kami gunakan untuk melakukan migration saat deployment:
- Buat satu stage khusus, yaitu migration. Apabila gagal, maka deployment juga gagal.
- Dalam stage migration, server aplikasi akan diakses, dan akan menjalankan script berikut dengan menyertakan tag dari docker image:
#!/bin/sh
export TAG=$1
docker-compose run app yarn run server:db:migrate
Catatan: server:db:migrate
merupakan alias yang telah kami buat untuk sequelize db:migrate
.
Yang Perlu Diperhatikan
Walaupun terlihat aman karena keberadaan fitur rollback, migration script tetaplah hanya sebuah wrapper untuk melakukan perubahan pada database. Artinya, apabila kita melakukan perubahan yang beresiko pada migration script seperti melakukan drop table atau column, maka migration script tersebut tetap beresiko. Dalam project Document Builder, kami menggunakan prinsip berikut:
- Jangan ubah/hapus migration script yang sudah ter-deploy: Hal ini sangat penting, karena script tersebut akan dibaca kembali saat dilakukan migration atau undo migration dan Sequelize tidak tahu bahwa migration script tersebut pernah berubah (atau dihapus) sebelumnya. Anggota tim Anda yang lain juga bisa jadi tidak tahu dan terpaksa melakukan undo migration dan migrate kembali.
- Hati-hati dengan migration script yang menghapus tabel/kolom: Artinya bahwa data yang ada akan hilang, gunakan dengan bijak.
- Direkomendasikan untuk satu perintah SQL untuk satu migration script: Ini lebih ke personal preference dan kemudahan dalam penulisan kode. Perhatikan bahwa menulis beberapa perintah dalam satu migration script tidak semudah menuliskan method
queryInterface
beberapa kali, karena apabila perintah tersebut gagal di tengah-tengah script, maka migration script tersebut tidak bisa melakukan rollback di tengah-tengah juga. Dalam database, terdapat konsep transaction. Transaction ini memastikan bahwa seluruh pekerjaan yang dilakukan dalam 1 transaction akan berhasil atau tidak sama sekali (atomicity). Maka dari itu, Anda harus membungkus seluruh perintah SQL tersebut dalam 1 transaction. Sayangnya, migration pada Sequelize tidak menggunakan transaction secara otomatis.
Tambahan: Testing pada Migration Script
Anda bisa juga melakukan testing terhadap migration script Anda. Testing berguna untuk setidaknya memastikan bahwa migration akan selalu berhasil sebelum deploy. Artikel ini membahas tentang testing menggunakan in-memory database.
Kesimpulan
Migration script mempermudah kita dalam melakukan migration pada database. Kami juga telah menunjukkan contoh penggunaan migration script, dan cara kami dalam melakukan migration dalam CI/CD. Dalam menggunaka n migration script, ada juga beberapa hal yang perlu diperhatikan agar penggunaan menjadi aman.
Sekian penjelasan saya dalam migration menggunakan Sequelize. Jangan lupa untuk melihat artikel oleh anggota tim yang lain.