Cài đặt golang & viết, run database migration trong golang


Golang (Go) ngày càng trở nên phổ biến trong phát triển ứng dụng backend nhờ hiệu suất cao, cú pháp đơn giản và hệ sinh thái phong phú. Một trong những tác vụ quan trọng khi xây dựng ứng dụng là quản lý database migration – giúp kiểm soát phiên bản của cơ sở dữ liệu một cách linh hoạt và an toàn.
Trong bài viết này, chúng ta sẽ tìm hiểu cách cài đặt Golang, thiết lập môi trường lập trình, và hướng dẫn cách viết, chạy database migration trong Golang. Bài viết sẽ cung cấp những hướng dẫn thực tế để bạn có thể áp dụng ngay vào dự án của mình.
Install Golang
Để cài đặt golang, chúng ta truy cập vào trang chủ của golang để chọn phiên bản cần cài đặt. Trong bài viết này tôi sử dụng phiên bản 1.23.7 linux/amd64 trên ubuntu 20. Trong series này tôi chỉ hướng dẫn làm việc với ubuntu nhé.
Tiếp theo tải version golang bằng curl.
curl -OL https://golang.org/dl/go1.23.7.linux-amd64.tar.gz
Để xác minh tính toàn vẹn của tệp bạn đã tải xuống, hãy chạy lệnh SHA256SUM
để check.
sha256sum go1.23.7.linux-amd64.tar.gz
// Kết quả
6924efde5de86fe277676e929dc9917d466efa02fb934197bc2eba35d5680971 go1.23.7.linux-amd64.tar.gz
Giải nén và chỉ định thư mục giải nén
sudo tar -C /usr/local -xvf go1.23.7.linux-amd64.tar.gz
Mặc dù /usr/local/go
là vị trí được khuyến nghị để cài đặt Go, các bạn cũng có thể sử dụng đường dẫn khác cũng được.
Tiếp theo, thiết lập biến môi trường cho Go bằng cách chỉnh sửa tệp .profile
trong thư mục home của người dùng.
sudo nano ~/.profile
Sau đó, thêm thông tin sau vào cuối tệp của bạn:
// content .profile
. . .
export PATH=$PATH:/usr/local/go/bin
Tiếp theo, hãy refresh .profile
của bằng cách chạy lệnh sau:
source ~/.profile
Kiêm tra phiên bản của golang.
go version
Kết quả như sau:
Ngoài ra chúng ta có thể dùng gvm
để cài đặt và quản lý version Golang. Đây là link tham khảo cách cài đặt của gvm nhé: https://github.com/moovweb/gvm
Install golang-migrate
golang-migrate
là một thư viện và công cụ dòng lệnh (CLI) dùng để quản lý database migrations trong các dự án sử dụng Go. Nó giúp bạn tạo, áp dụng, và hoàn tác (rollback) các thay đổi trong cơ sở dữ liệu một cách có kiểm soát.
Các tính năng chính:
Hỗ trợ nhiều database drivers như PostgreSQL, MySQL, SQLite, MongoDB...
Hỗ trợ up (nâng cấp) và down (hạ cấp) migrations.
Có thể sử dụng dưới dạng CLI hoặc nhúng vào code Go với package
migrate
.Hỗ trợ tải migration từ nhiều nguồn khác nhau (tệp cục bộ, Amazon S3, GitHub...).
Đây là tài liệu tích hợp, tôi sử dụng ubuntu nên sẽ sử dụng các lệnh sau
curl -L https://packagecloud.io/golang-migrate/migrate/gpgkey | apt-key add -
echo "deb https://packagecloud.io/golang-migrate/migrate/ubuntu/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/migrate.list
apt-get update
apt-get install -y migrate
Create a new migration
Bây giờ tôi sẽ tạo một thư mục mới cho dự án simple_bank
. Và bên trong, tôi sẽ tạo một thư mục db/migration
để lưu trữ tất cả các file migration.
mkdir simple_bank
cd simple_bank
mkdir -p db/migration
Sau đó, hãy tạo file migration đầu tiên để khởi tạo lược đồ cơ sở dữ liệu của simple_bank
.
migrate create -ext sql -dir db/migration -seq init_schema
Lệnh trên được sử dụng để tạo một migration mới trong thư mục db/migration
. Hãy phân tích từng phần của lệnh:
migrate create
: Lệnh dùng để tạo file migration mới.-ext sql
: Chỉ định phần mở rộng của file là.sql
(tức là migration sẽ dùng SQL thuần).-dir db/migration
: Chỉ định thư mục lưu migration làdb/migration
.-seq
: Tạo tên file migration với số thứ tự tuần tự (ví dụ:000001_init_schema.up.sql
và000001_init_schema.down.sql
).init_schema
: Tên của migration, giúp xác định mục đích của nó (ở đây là khởi tạo schema ban đầu).
Up/down migration
Như các bạn có thể thấy, 2 file migration đã được tạo ra.
Nói đơn giản, đây là một best practice khi viết migration cho database.
Up script: Dùng để thay đổi schema theo hướng tiến (ví dụ: tạo bảng, thêm cột...).
Down script: Dùng để hoàn tác thay đổi của up script (ví dụ: xóa bảng, xóa cột...).
Khi chạy lệnh migrate up
, các file up script trong thư mục db/migration
sẽ được thực thi tuần tự theo thứ tự phiên bản được đặt ở phần tiền tố của tên file.
Ngược lại, khi chạy lệnh migrate down
, các file down script trong thư mục db/migration
sẽ được thực thi tuần tự nhưng theo thứ tự ngược lại của phần tiền tố phiên bản.
Bây giờ, hãy mở file simple_bank.sql
mà chúng ta đã tạo trước đó. Tôi sẽ sao chép toàn bộ nội dung của file này và dán vào file init_schema.up.sql.
CREATE TABLE "accounts" (
"id" bigserial PRIMARY KEY,
"owner" varchar NOT NULL,
"balance" bigint NOT NULL,
"currency" varchar NOT NULL,
"created_at" timestamp NOT NULL DEFAULT (now())
);
CREATE TABLE "entries" (
"id" bigserial PRIMARY KEY,
"account_id" bigint NOT NULL,
"amount" bigint NOT NULL,
"created_at" timestamp NOT NULL DEFAULT (now())
);
CREATE TABLE "transfers" (
"id" bigserial PRIMARY KEY,
"from_account_id" bigint NOT NULL,
"to_account_id" bigint NOT NULL,
"amount" bigint NOT NULL,
"created_at" timestamp DEFAULT (now())
);
CREATE INDEX ON "accounts" ("owner");
CREATE INDEX ON "entries" ("account_id");
CREATE INDEX ON "transfers" ("from_account_id");
CREATE INDEX ON "transfers" ("to_account_id");
CREATE INDEX ON "transfers" ("from_account_id", "to_account_id");
COMMENT ON COLUMN "entries"."amount" IS 'can be negative or positive';
COMMENT ON COLUMN "transfers"."amount" IS 'must be positive';
ALTER TABLE "entries" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id");
ALTER TABLE "transfers" ADD FOREIGN KEY ("from_account_id") REFERENCES "accounts" ("id");
ALTER TABLE "transfers" ADD FOREIGN KEY ("to_account_id") REFERENCES "accounts" ("id");
Đối với file init_schema.down.sql, chúng ta cần hoàn tác các thay đổi do up script thực hiện. Trong trường hợp này, up script đã tạo 3 bảng: accounts
, transfers
và entries
, vì vậy down script sẽ xóa tất cả bằng câu lệnh DROP TABLE
.
DROP TABLE IF EXISTS entries;
DROP TABLE IF EXISTS transfers;
DROP TABLE IF EXISTS accounts;
Ở đây, chúng ta xóa bảng entries
và transfers
trước khi xóa bảng accounts
vì hai bảng này có ràng buộc foreign key tham chiếu đến accounts
.
OK, bây giờ các script migration đã sẵn sàng. Hãy thử chạy chúng bằng cách đọc các phần sau từ từ và bình tĩnh nhé.
Truy cập vào postgres container
Sử dụng lệnh sau để truy cập vào container.
docker run --name postgres16 -e POSTGRES_USER=root -e POSTGRES_PASSWORD=Abc@12345678 -p 54322:5432 -d postgres:16-alpine
Sau khi start được server database thì chúng ta có thể tiến hành truy cập vào container
docker exec -it postgres16 bin/bash
// or
docker exec -it postgres16 bin/sh
Vì đây là một container PostgreSQL, nó cũng cung cấp một số lệnh CLI để tương tác trực tiếp với máy chủ PostgreSQL từ shell.
Tạo/ Xóa database bên trong postgres container
psql -U root
Để tạo mới hoặc xóa database chúng ta sử dụng lệnh sau
create database simple_bank;
drop database simple_bank;
Tạo/ Xóa database bên ngoài postgres container
Bây giờ, từ bên ngoài container, chúng ta cũng có thể chạy trực tiếp lệnh tạo/xóa database bằng lệnh docker exec
docker exec -it postgres16 psql -U root -c "CREATE DATABASE simple_bank;"
docker exec -it postgres16 psql -U root -c "DROP DATABASE simple_bank;"
Và tất nhiên cũng có thể truy cập vào database bằng lệnh docker exec
docker exec -it postgres16 psql -U root simple_bank
Viết Makefile
1️⃣ Makefile là gì?
Makefile là một tập tin chứa các lệnh để tự động hóa các tác vụ trong dự án, thường được sử dụng với lệnh
make
.Giúp đơn giản hóa quá trình thiết lập và chạy dự án bằng cách gom nhóm các lệnh dài thành các lệnh ngắn gọn hơn.
2️⃣ Cách tạo và sử dụng Makefile
Tạo một file Makefile
trong thư mục dự án và thêm các lệnh sau:
DB_NAME=simple_bank
DB_USER=postgres
DB_PASSWORD=Abc@12345678
DB_CONTAINER=postgres16
CONTAINER_DB_PORT=5432
HOST_PORT=54322
postgres:
docker run --name $(DB_CONTAINER) -e POSTGRES_USER=$(DB_USER) -e POSTGRES_PASSWORD=$(DB_PASSWORD) -p $(HOST_PORT):$(CONTAINER_DB_PORT) -d postgres:16-alpine
createdb:
docker exec -it $(DB_CONTAINER) bash -c "until pg_isready -U $(DB_USER); do sleep 1; done && psql -U $(DB_USER) -c 'CREATE DATABASE $(DB_NAME);'"
dropdb:
docker exec -it $(DB_CONTAINER) psql -U $(DB_USER) -c "DROP DATABASE $(DB_NAME);"
.PHONY: postgres createdb dropdb
Khởi chạy PostgreSQL: Dùng Docker để chạy container
postgres16
với user, password, và cổng được cấu hình.Tạo database: Kiểm tra PostgreSQL sẵn sàng rồi tạo database
simple_bank
.Xóa database: Xóa database
simple_bank
trong container..PHONY
: Đảm bảo các target luôn chạy, không bị nhầm với file cùng tên.
Tiến hành dừng các container đang chạy và test hoạt động của Makefile
docker stop postgres16
docker container prune
Vậy giờ tất cả các container đã dừng và được xóa, hãy thử chạy file make nhé.
make postgres
make createdb
Sử dụng gui để kiểm tra xem database mới đã được tạo chưa
Okay, có vể mọi thứ đã hoạt động rồi chúng ta cùng nhau qua bước tiếp theo là viết các lệnh migrate vào Makefile
migrateup:
migrate -path db/migration -database "postgresql://$(DB_USER):$(DB_PASSWORD)@localhost:$(DB_PORT)/$(DB_NAME)?sslmode=disable" -verbose up
migrate
: Lệnh chạy công cụmigrate
.-path db/migration
: Chỉ định thư mục chứa các file migration (SQL hoặc Go script)-database "postgresql://$(DB_USER):$(DB_PASSWORD)@
localhost
:$(DB_PORT)/$(DB_NAME)?sslmode=disable
": Kết nối đến database PostgreSQL, vì chạy trên local nên tôi sẽsslmode=disable
-verbose
: Hiển thị log chi tiết.up
: Áp dụng tất cả các migration mới chưa được thực thi.
Đây là Makefile sau khi thêm các câu lệnh migration up
và down
DB_NAME=simple_bank
DB_USER=postgres
DB_PASSWORD=Abc@12345678
DB_CONTAINER=postgres16
CONTAINER_DB_PORT=5432
HOST_PORT=54322
postgres:
docker run --name $(DB_CONTAINER) -e POSTGRES_USER=$(DB_USER) -e POSTGRES_PASSWORD=$(DB_PASSWORD) -p $(HOST_PORT):$(CONTAINER_DB_PORT) -d postgres:16-alpine
createdb:
docker exec -it $(DB_CONTAINER) bash -c "until pg_isready -U $(DB_USER); do sleep 1; done && psql -U $(DB_USER) -c 'CREATE DATABASE $(DB_NAME);'"
dropdb:
docker exec -it $(DB_CONTAINER) psql -U $(DB_USER) -c "DROP DATABASE $(DB_NAME);"
migrateup:
migrate -path db/migration -database "postgresql://$(DB_USER):$(DB_PASSWORD)@localhost:$(HOST_PORT)/$(DB_NAME)?sslmode=disable" -verbose up
migratedown:
migrate -path db/migration -database "postgresql://$(DB_USER):$(DB_PASSWORD)@localhost:$(HOST_PORT)/$(DB_NAME)?sslmode=disable" -verbose down
.PHONY: postgres createdb dropdb migrateup migratedown
Okay, cùng nhau test nhé, bắt đầu thử chạy lệnh make migratedown
, sử dụng dbeaver để kiểm tra, tất cả các bảng đã biến mất ngoại trừ bảng schema_migrations
Tiếp tục thử lại với lệnh make migrateup
Okay, có vẻ mọi thứ hoạt động ổn định, bài viết hôm nay về migration database xin kết thúc tại đây. Cám ơn các bạn đã đọc và hẹn gặp lại trong các bài viết lần sau.
Tài liệu tham khảo: https://dev.to/techschoolguru/how-to-write-run-database-migration-in-golang-5h6g
Subscribe to my newsletter
Read articles from Eminel directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Eminel
Eminel
Hello, my name is Eminel a software engineer specializing in scalable software architecture, microservices, and AI-powered platforms. Passionate about mentoring, performance optimization, and building solutions that drive business success.