LoginSignup
9
5

Go × PostgreSQL × SQLBoiler (ORM) × golang-migrate (マイグレーション) の開発環境を構築する

Last updated at Posted at 2023-07-16

毎回環境を作るときに何かしらド忘れしてしまい手こずってしまうので忘れないようにメモ。

はじめに

  • 下記の知識や経験があることを前提にしてます
    • Go言語の基礎を理解している
    • Dockerと docker-compose の基礎を理解している
    • バックエンド開発やデータベースの基礎を理解している
  • ORMの使い方や各種コマンドの詳しい説明はしません
  • パスなどはご自身のプロジェクトの構成に合わせて適宜変更してください

作ったもののサンプルです。

開発環境

  • Go1.20
  • PostgreSQL 15
  • Docker
  • docker-compose

使用するツール

SQLBoler v3.7.1

golang-migrate v4.15.2

手順① Docker と docker-compose で Go と PostgreSQL を起動

Dockerfile作成

Go

FROM golang:1.20

WORKDIR /app

RUN go install github.com/volatiletech/sqlboiler@latest
RUN go install github.com/volatiletech/sqlboiler/drivers/sqlboiler-psql@latest
RUN go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@v4.15.2

# この辺はプロジェクトのディレクトリ構成に合わせてください
COPY ./src .
  • SQLBoiler と golang-migrate もインストールしておきます
  • ORMのコマンドとマイグレーションのコマンドもDocker上で叩けるようにします

PostgreSQL

FROM postgres:15

docker-compose.yml作成

version: "3.7"

services:
  app:
    build:
      context: .
      dockerfile: ./docker/golang/Dockerfile
    volumes:
      - ./:/app
    command: ["go", "run", "src/cmd/main.go"]
    ports:
      - "8080:8080"
    environment:
      - DB_HOST=${DB_HOST:-db}
      - DB_PORT=${DB_PORT:-5432}
      - DB_USER=${POSTGRES_USER:-postgres}
      - DB_PASSWORD=${POSTGRES_PASSWORD:-password}
      - DB_NAME=${POSTGRES_DB:-postgres}
    depends_on:
      - db
    networks:
      - app_network

  db:
    build:
      context: .
      dockerfile: ./docker/postgresql/Dockerfile
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      - POSTGRES_USER=${POSTGRES_USER:-postgres}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-password}
      - POSTGRES_DB=${POSTGRES_DB:-postgres}
    networks:
      - app_network

volumes:
  postgres_data:

networks:
  app_network:

起動してみる

エントリーポイントとなる main.go を作成

package main

import (
	"database/sql"
	"fmt"
	"log"
	"net/http"
	"os"

	_ "github.com/lib/pq"
)

func main() {

	// DB接続
	dsn := fmt.Sprintf(
		"host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=Asia/Tokyo",
		os.Getenv("DB_HOST"),
		os.Getenv("DB_USER"),
		os.Getenv("DB_PASSWORD"),
		os.Getenv("DB_NAME"),
		os.Getenv("DB_PORT"),
	)
	db, err := sql.Open("postgres", dsn)
	if err != nil {
		log.Fatal("failed to init database: ", err)
	}

	err = db.Ping()
	if err != nil {
		log.Fatal("failed to connect database: ", err)
	}

	log.Default().Println("success to connect db!!")

	// HTTPサーバーを起動
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Hello, World!"))
	})

	err = http.ListenAndServe(":8080", nil)
	if err != nil {
		log.Fatal("failed to serve: ", err)
	}
	log.Default().Println("Server started on port: 8080")
}

envファイル作成

  • ルートディレクトリに.envファイルを作成し、下記を追加
DB_HOST=db
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=password
DB_NAME=sample
  • ここで設定した環境変数は docker-compose で起動する時に読み取ってくれます
# docker-compose で設定したやつ。
 environment:
      - DB_HOST=db
      - DB_PORT=5432
      - DB_USER=${POSTGRES_USER:-postgres}
      - DB_PASSWORD=${POSTGRES_PASSWORD:-password}
      - DB_NAME=${POSTGRES_DB:-postgres}

docker起動

docker-compose up -d --build

docker-desktop で下記を確認してください

  • http server が起動できてること
  • DBが起動してること

手順② マイグレーションファイルの作成

golang-migate のコマンドを叩いてマイグレーションファイルを生成

  • 下記のように実行できます
docker-compose exec app migrate create -ext sql -dir ${ファイルを生成する先のディレクトリ} -seq ${テーブル名}
  • 実行するとこんな感じです
docker-compose exec app migrate create -ext sql -dir ./src/database/migrations -seq tasks
  • 実行に成功したらマイグレーションファイルが生成されてるはずです。
src
 ├── database
    │   ├── migrations
    │   │   ├── 000001_tasks.down.sql
    │   │   └── 000001_tasks.up.sql

マイグレーションファイルに記述

upファイル

CREATE TABLE tasks (
    id varchar(36) PRIMARY KEY,
    title TEXT NOT NULL,
    created_at TIMESTAMP NOT NULL
);

downファイル

DROP TABLE IF EXISTS tasks;

手順③ マイグレーションファイルの内容をDBに反映する

docker-compose exec app migrate -database "postgres://${ユーザー名}:${パスワード}@${ホスト}:${ポート}/${データベースの名前}?sslmode=disable" -path ${マイグレーションファイルのパス} up

こんな感じ

docker-compose exec app migrate -database "postgres://postgres:password@db:5432/postgres?sslmode=disable" -path ./src/database/migrations up

ここまでできるとテーブルが作成されてるはずです。

  • PostgreSQL の中に入る
docker-compose exec db psql --host=localhost --port=5432 --username=postgres
  • \l でデータベース一覧を確認
postgres=# \l
                                                          List of databases
   Name    |  Owner   | Encoding |  Collate   |   Ctype    | ICU Locale | Locale Provider |   Access privileges   
-----------+----------+----------+------------+------------+------------+-----------------+-----------------------
 postgres  | postgres | UTF8     | en_US.utf8 | en_US.utf8 |            | libc            | 
 sample    | postgres | UTF8     | en_US.utf8 | en_US.utf8 |            | libc            | 
 template0 | postgres | UTF8     | en_US.utf8 | en_US.utf8 |            | libc            | =c/postgres          +
           |          |          |            |            |            |                 | postgres=CTc/postgres
 template1 | postgres | UTF8     | en_US.utf8 | en_US.utf8 |            | libc            | =c/postgres          +
           |          |          |            |            |            |                 | postgres=CTc/postgres
(4 rows)
  • (もしデータベースを自分で作成していたら) \c ${作成したデータベースの名前} のコマンドを叩いてデータベースを切り替えてください
postgres=# \c sample
You are now connected to database "sample" as user "postgres".
sample=# 
  • /dt でテーブル一覧を表示する
sample=# \dt
               List of relations
 Schema |       Name        | Type  |  Owner   
--------+-------------------+-------+----------
 public | schema_migrations | table | postgres
 public | tasks             | table | postgres
(2 rows)
  • \d ${テーブル名}で作成されたフィールドの一覧を確認
sample=# \d tasks
                           Table "public.tasks"
   Column   |            Type             | Collation | Nullable | Default 
------------+-----------------------------+-----------+----------+---------
 id         | character varying(36)       |           | not null | 
 title      | text                        |           | not null | 
 created_at | timestamp without time zone |           | not null | 
Indexes:
    "tasks_pkey" PRIMARY KEY, btree (id)
  • 上記のように作成したテーブルとフィールドが存在していたら成功です。

ちなみに down と force のコマンドは下記のとおりです。

down

docker-compose exec app migrate -database "postgres://${ユーザー名}:${パスワード}@${ホスト}:${ポート}/${データベースの名前}?sslmode=disable" -path ${マイグレーションファイルのパス} down

force

docker-compose exec app migrate -database "postgres://${ユーザー名}:${パスワード}@${ホスト}:${ポート}/${データベースの名前}?sslmode=disable" -path ${マイグレーションファイルのパス} force ${バージョン}

手順④ DBのスキーマからGoのコードを生成する(ORM)

SQLBoilerを使ってDBスキーマからコードを自動生成します。

tomlファイルの作成

  • SQLBoiler設定ファイルを作成します
  • sqlboiler.toml というファイルをルートに作成して、DBの設定に応じて下記を追加してください
[psql]
  dbname = "sample"
  host   = "db"
  port   = 5432
  user   = "postgres"
  pass   = "password"
  sslmode = "disable"
  schema = "public"

SQLBoilerのコマンドを叩いてスキーマからコード生成

  • 下記のようなフォーマットで生成できます
docker-compose exec app sqlboiler psql --output ${出力先のパス名} --pkgname models --wipe
  • 今回は下記のようにします。
docker-compose exec app sqlboiler psql --output ./src/database/models --pkgname models --wipe

実行に成功すると指定したディレクトリにコードが生成されます。

└── src
    ├── cmd
    │   └── main.go
    └── database
        ├── migrations
        │   ├── 000001_tasks.down.sql
        │   └── 000001_tasks.up.sql
        └── models // DBのスキーマから自動生成されたファイル
            ├── boil_main_test.go
            ├── boil_queries.go
            ├── boil_queries_test.go
            ├── boil_suites_test.go
            ├── boil_table_names.go
            ├── boil_types.go
            ├── psql_main_test.go
            ├── psql_suites_test.go
            ├── psql_upsert.go
            ├── schema_migrations.go
            ├── schema_migrations_test.go
            ├── tasks.go
            └── tasks_test.go

生成後は go mod tidyを忘れず実行しましょう。

go mod tidy
  • 生成したコードを使ってDBにアクセスできます。
package repository

import (
	"context"
	"database/sql"
	"github/Takenari-Yamamoto/golang-dev-env/src/database/models" // SQLBoilerから生成されたモデル

	"github.com/volatiletech/sqlboiler/boil"
)

// 全件取得
func GetAllTasks(ctx context.Context, db *sql.DB) (tasks []*models.Task, err error) {
	res, err := models.Tasks().All(ctx, db)
	if err != nil {
		return nil, err
	}
	return res, nil
}

// 1件取得
func GetTask(ctx context.Context, db *sql.DB, id string) (task *models.Task, err error) {
	res, err := models.FindTask(ctx, db, id)
	if err != nil {
		return nil, err
	}
	return res, nil
}

// 作成
func CreateTask(ctx context.Context, db *sql.DB, task *models.Task) (err error) {
	err = task.Insert(ctx, db, boil.Infer())
	if err != nil {
		return err
	}
	return nil
}

// 更新
func UpdateTask(ctx context.Context, db *sql.DB, task *models.Task) (err error) {
	_, err = task.Update(ctx, db, boil.Infer())
	if err != nil {
		return err
	}
	return nil
}

// 1件削除
func DeleteTask(ctx context.Context, db *sql.DB, task *models.Task) (err error) {
	_, err = task.Delete(ctx, db)
	if err != nil {
		return err
	}
	return nil
}

おまけ

Makefileで実行できるようにしておくと便利です。

create-migration-file:
	docker-compose exec app migrate create -ext sql -dir ./src/database/migrations -seq ${TABLE_NAME}

migrate-up:
	docker-compose exec app migrate -database "postgres://postgres:password@db:5432/sample?sslmode=disable" -path ./src/database/migrations up

migrate-down:
	docker-compose exec app migrate -database "postgres://postgres:password@db:5432/sample?sslmode=disable" -path ./src/database/migrations down

migrate-force:
	docker-compose exec app migrate -database "postgres://postgres:password@db:5432/sample?sslmode=disable" -path ./src/database/migrations force ${VERSION}

gen-code-from-db:
	docker-compose exec app sqlboiler psql --output ./src/database/models --pkgname models --wipe && cd src && go mod tidy
9
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
5