Menjalankan Liquibase dengan Docker

Penggunaan aplikasi untuk migrasi database bukanlah sesuatu yang asing. Di website ini saja sudah ada beberapa artikel, yaitu:

Di ArtiVisi, kita saat ini sudah tidak lagi menggunakan Liquibase, beralih ke FlywayDB

Praktek yang biasanya kita lakukan di ArtiVisi adalah menggabungkan file migrasi dengan aplikasi. Sehingga ketika aplikasi dideploy, tools migrasi akan mengecek ke database, script mana yang belum dijalankan, dan mana yang sudah dijalankan. Kemudian dia akan menjalankan script-script yang belum pernah dieksekusi.

Akan tetapi, sebetulnya tools migrasi tidak harus dijalankan bersamaan dengan deployment. Kita juga bisa jalankan secara standalone melalui command line atau docker. Ini dibutuhkan diantaranya ketika aplikasi kita menggunakan replication, aplikasi dijalankan menjadi beberapa instance. Misalnya kalau kita deploy di Kubernetes. Skenarionya kira-kira seperti yang dijelaskan di artikel ini.

Untuk itu, kita perlu menjalankan database migration tools secara terpisah. Sehingga bisa dieksekusi terlepas dari kapan kita mendeploy aplikasi. Tentunya kita harus mendesain aplikasi kita sedemikian rupa sehingga kompatibel dengan skema database before dan after script migrasi dijalankan.

Liquibase bisa dijalankan melalui Docker, seperti dijelaskan di dokumentasi resminya. Berikut kita bahas langkah-langkahnya.

Struktur Folder

Semua aplikasi migrasi database memiliki file-file migration yang akan dieksekusi untuk mengubah skema database. Kemudian kita juga membutuhkan folder untuk konfigurasi Liquibase. Sehingga struktur foldernya menjadi seperti ini

Struktur Folder Liquibase

Folder tersebut berisi script migrasi dan konfigurasi.

Script Migrasi

Script migrasi adalah file yang akan dieksekusi untuk mengubah isi database. Liquibase mendukung beberapa format file, diantaranya:

  • SQL
  • XML
  • JSON
  • YAML

Saya biasanya menggunakan format SQL, supaya lebih familiar bagi programmer dan database administrator. Kekurangan format SQL adalah, kita harus membuat script berbeda untuk tiap merek database. Ini tidak menjadi masalah bila kita tidak berencana berganti-ganti merek database.

Untuk database kosong, biasanya kita membuat file skema awal seperti ini

--liquibase formatted sql
--changeset endy:1
create table customer (
    id varchar(36),
    customer_name varchar(200) not null,
    email varchar(100) not null,
    mobile_phone varchar(50) not null,
    primary key (id)
);

create table merchant (
    id varchar(36),
    merchant_name varchar(20) not null,
    primary key (id)
);

Ada beberapa hal yang harus diperhatikan pada file tersebut:

  1. Tiap script harus diawali dengan --liquibase formatted sql. Ini untuk memberi tahu Liquibase bahwa scriptnya berbentuk SQL, bukan JSON, YAML, atau XML.
  2. Author dan ID changeset. Informasi ini digunakan Liquibase untuk menentukan apakah file tersebut sudah pernah dijalankan. Liquibase akan melihat variabel author, id, dan nama file script. Bila kombinasi tersebut sudah ada di database, maka script tidak akan dijalankan lagi.

Sebetulnya masih banyak atribut lain seperti failOnError, runInXxx, dan sebagainya. Informasi lengkapnya bisa dibaca di dokumentasi resminya

Kemudian, bila kita ingin melakukan perubahan lanjutan pada versi berikutnya, kita bisa buat file baru di dalam folder migrations lagi seperti ini

--liquibase formatted sql
--changeset endy:2

alter table customer 
    rename column mobile_phone to phone_number;

Konfigurasi

Konfigurasi pertama adalah informasi koneksi ke database. Isinya seperti ini, kita simpan di file bernama liquibase.properties

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://host.docker.internal:33061/belajardb
username=belajar
password=belajar123

searchPath=/liquibase/changelog
changelogFile=changelog.xml

Penjelasannya sebagai berikut:

  • driver : library koneksi database yang digunakan. Isinya berbeda antar merek. Programmer Java biasanya sudah paham apa itu database driver. Programmer non-Java, bisa googling dengan keyword jdbc driver merek-database, misalnya jdbc driver postgresql
  • url : informasi tentang alamat/nama server database, port, dan nama database
  • username dan password : credential untuk connect ke database
  • searchPath : lokasi tempat mencari file script migrasi
  • changelogFile : menunjukkan lokasi file changelog. Changelog adalah file yang berisi daftar changeset. Changeset secara sederhana adalah file migrasi berisi instruksi perubahan database. Jadi changelog file, adalah file daftar isi changeset.

Driver Database

Driver database adalah library yang digunakan Liquibase untuk terhubung ke database. Karena Liquibase dibuat dengan Java, maka library ini juga dibuat dengan Java, yang disebut dengan JDBC (Java DataBase Connectivity) Driver. Untuk produk database yang fully open source seperti PostgreSQL, kita bisa langsung menjalankan Liquibase Docker Container. Akan tetapi, ada juga driver opensource yang hanya mengijinkan redistribusi dalam bentuk source code, tidak boleh membagikannya dalam bentuk binary, misalnya MySQL dan MongoDB. Untuk kedua database ini, docker image Liquibase tidak menyertakannya dalam image. Sehingga kita harus membuat image baru yang berisi Liquibase ditambah driver MySQL / MongoDB.

Caranya tidak sulit, cukup buat Dockerfile berisi dua baris berikut ini

FROM liquibase
RUN lpm add mysql --global

Setelah itu, kita build imagenya dengan perintah berikut

docker build --platform linux/amd64 -t liquibase-mysql .

Saya menggunakan opsi --platform linux/amd64 karena saya jalankan di laptop Apple M1 yang platformnya arm64. Sehingga kalau tidak pakai opsi tersebut, imagenya tidak akan bisa jalan di server linux.

Docker Compose

Untuk mengetes image tersebut, kita bisa jalankan database MySQL menggunakan docker compose. File konfigurasinya seperti ini

services:
  db-belajar:
    image: mysql:8
    platform: linux/x86_64
    environment:
      - MYSQL_RANDOM_ROOT_PASSWORD=yes
      - MYSQL_DATABASE=belajardb
      - MYSQL_USER=belajar
      - MYSQL_PASSWORD=belajar123
    ports:
      - 33061:3306
    volumes:
      - ./db-belajar:/var/lib/mysql

Pastikan nama database, port, username, dan passwordnya sesuai dengan konfigurasi di liquibase.properties. Kemudian jalankan dengan perintah berikut

docker compose up

Buka terminal baru, kemudian periksa apakah container sudah berjalan dengan perintah berikut

docker ps

Outputnya seperti ini, perhatikan kolom NAMES di paling kanan:

CONTAINER ID   IMAGE     COMMAND                  CREATED          STATUS          PORTS                                NAMES
e40e7531eeeb   mysql:8   "docker-entrypoint.s…"   16 minutes ago   Up 16 minutes   33060/tcp, 0.0.0.0:33061->3306/tcp   db-migration-liquibase-db-belajar-1

Setelah database MySQL up, kita bisa login ke dalam MySQL dengan perintah berikut

docker exec -it db-migration-liquibase-db-belajar-1 mysql -u belajar belajardb -p    

Outputnya seperti ini

Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.4.3 MySQL Community Server - GPL

Copyright (c) 2000, 2024, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> 

Setelah login, cek isi database dengan perintah berikut:

show tables;

Outputnya seperti ini

mysql> show tables;
Empty set (0.01 sec)

Itu menunjukkan bahwa tidak ada tabel dalam database. Memang seharusnya begitu, karena databasenya baru saja dibuat.

Menjalankan Migrasi Database

Buka terminal baru lagi (saat ini sudah ada 2 : docker compose dan mysql console), kemudian jalankan docker image berisi Liquibase + driver MySQL dengan perintah berikut

docker run --rm -v ${PWD}/changelog:/liquibase/changelog liquibase-mysql --defaults-file=/liquibase/changelog/liquibase.properties update

Outputnya seperti ini

####################################################
##   _     _             _ _                      ##
##  | |   (_)           (_) |                     ##
##  | |    _  __ _ _   _ _| |__   __ _ ___  ___   ##
##  | |   | |/ _` | | | | | '_ \ / _` / __|/ _ \  ##
##  | |___| | (_| | |_| | | |_) | (_| \__ \  __/  ##
##  \_____/_|\__, |\__,_|_|_.__/ \__,_|___/\___|  ##
##              | |                               ##
##              |_|                               ##
##                                                ## 
##  Get documentation at docs.liquibase.com       ##
##  Get certified courses at learn.liquibase.com  ## 
##                                                ##
####################################################
Starting Liquibase at 14:38:42 using Java 17.0.13 (version 4.29.2 #3683 built at 2024-08-29 16:45+0000)
Liquibase Version: 4.29.2
Liquibase Open Source 4.29.2 by Liquibase
Running Changeset: migrations/changelog-2024101701.sql::1::endy
Running Changeset: migrations/changelog-2024101702.sql::1::endy

UPDATE SUMMARY
Run:                          2
Previously run:               0
Filtered out:                 0
-------------------------------
Total change sets:            2

Liquibase: Update has been successful. Rows affected: 2
Liquibase command 'update' was executed successfully.

Liquibase sudah berhasil menjalankan kedua script migrasi kita. Kembali ke terminal yang berisi mysql console, dan cek lagi isi databasenya. Outputnya seharusnya seperti ini

mysql> show tables;
+-----------------------+
| Tables_in_belajardb   |
+-----------------------+
| customer              |
| databasechangelog     |
| databasechangeloglock |
| merchant              |
+-----------------------+
4 rows in set (0.14 sec)

Kita bisa melihat daftar changeset yang sudah dijalankan dengan melakukan query select ke tabel databasechangelog. Outputnya seperti ini

mysql> select * from databasechangelog \G
*************************** 1. row ***************************
           ID: 1
       AUTHOR: endy
     FILENAME: migrations/changelog-2024101701.sql
 DATEEXECUTED: 2024-11-06 14:38:43
ORDEREXECUTED: 1
     EXECTYPE: EXECUTED
       MD5SUM: 9:969a320fb9ed60b12739fca9f32b775c
  DESCRIPTION: sql
     COMMENTS: 
          TAG: NULL
    LIQUIBASE: 4.29.2
     CONTEXTS: NULL
       LABELS: NULL
DEPLOYMENT_ID: 0903923765
*************************** 2. row ***************************
           ID: 2
       AUTHOR: endy
     FILENAME: migrations/changelog-2024101702.sql
 DATEEXECUTED: 2024-11-06 14:38:43
ORDEREXECUTED: 2
     EXECTYPE: EXECUTED
       MD5SUM: 9:03057b5f64f2e1359b2453a1a77422f1
  DESCRIPTION: sql
     COMMENTS: 
          TAG: NULL
    LIQUIBASE: 4.29.2
     CONTEXTS: NULL
       LABELS: NULL
DEPLOYMENT_ID: 0903923765
2 rows in set (0.01 sec)

Demikianlah cara menjalankan Liquibase secara standalone tanpa dibundel ke dalam aplikasi. Bila ingin mencoba, source code bisa didapatkan di repo Github.

Semoga bermanfaat.