Goでマイグレーションしてみる!!!!
概要
今回の記事は駆け出しエンジニアと言っていいのかどうかすらわからないくらいに弱々エンジニアの自分が書く記事ですので適当に読んでください!そして何かしらの不備があったら是非指摘してください!お願いします!!自分の経験として最初のデータベースを準備してそれにデータを入れて確認してみるということが非常に難しかったのでそこの言語化をして自分の身になればいいなーと思って書きます!
最近golangを触ってます。1年くらい前に1回golangかっこいいなーって思いながら勉強してみたのですがその時は挫折してしまったのですが最近触ったら意外といけるなってなったのでお楽しみ程度に触ってますー
なんだかんだ1年経つと人間は成長しているものですね笑
特にほかの言語を極めたとかではないんですけどAPIの開発を行ってみたいなと感じたのでGinとかを触ってます。
簡単なCRUDが行えるアプリケーションの作成に関する記事もこれから書いてみたいなーと思っているのでぜひみてみてくださいー
今回の記事では以下の記事を参考に私が学習していてこれってなんだろうとかなった箇所に関して書いていきます。(この方の記事でかなり流れが掴めたと自分では感じてます。)
準備しておく環境
- Docker Desktopを使用することができること
- goを実行することができること
- 何かしらのエディター(これはなくてもいいです)
確かwindowsでDocker Desktopの環境構築するのは少し手間取った気がします笑
こちらの記事とか参考にしてください!
大まかな流れ
今回のデータベースを準備してそこにカラムを追加することです!
このデータベースとかを準備するのが一番難しいと感じますよね。
それでは流れを見ていきます。
- Goのプロジェクトを準備、gormなどをプロジェクト内で使用できるようにしておく
- .envファイル、docker-compose.ymlをディレクトリに作成する
-
docker compose up
でデータベースサーバーを立てる - マイグレーションのもとになるファイルを作成する
- 実際にマイグレーションを行う
-
.env 環境変数やデータベースにアクセスする際のデータを書いておくファイル。
-
gorm golangのためのORM。これを使用することでSQLを使用しないでコードからSQLを実行することができる。また、マイグレーションなども行うことができる。
↓gormの公式ドキュメント
実際にやってみる
goのプロジェクトの準備、依存関係のインストール
まずは使用するプロジェクトのルートディレクトリに移動します。
今回はtodo-test
という名前のプロジェクトで作業をしていきます。ここのプロジェクトの名前は自分の好きなように変更してください!
それではまずは
go mod init (プロジェクトの名前)
するとgo.mod
ファイルができるかと思います。このファイルは使用されるモジュールの依存関係を管理しているファイルです。
次に
go get -u github.com/gin-gonic/gin
を実行します。これはGin(golangのフレームワーク)を使用することができるようにするためのコマンドです。これを実行するとターミナルにたくさん文字が表示されてgo.mod
ファイルにもたくさん書きこまれます。加えてgo.sum
が作成されているかと思います。これはファイル内のモジュールの依存関係をハッシュ化したものを保存しているらしい...難しい!笑
ただ使いこなすだけではなく依存関係とかそういう細かいところもしっかりわかるようになりたいですね
詳しくは以下の記事を参考にしてください。
gorm,sqlite用のドライバー、postgressql用のドライバーのインストールをします。以下のコマンドを1行ずつ実行します。
go get -u gorm.io/gorm
go get -u gorm.io/driver/sqlite
go get -u gorm.io/driver/postgres
go get github.com/joho/godotenv
ここまできたらgo.mod
ファイルは以下のようになっていると思います。これはあくまで1部ですのでご注意ください。
module todo-test
go 1.23.2
require (
github.com/bytedance/sonic v1.12.4 // indirect
github.com/bytedance/sonic/loader v0.2.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
...
たくさんのパッケージがインストールされているかと思います。今回の記事ではGinを使用することはないのですが、一応インストールしておきました。(今後の記事で使用することになると思うので)
.envファイル、docker-compose.ymlの準備
touch .env
touch docker-compose.yml
を実行してそれぞれファイルをルートディレクトリに作成します。
作成したファイルを次のように編集します
POSTGRES_USER=todouser
POSTGRES_PASSWORD=gintodopass
POSTGRES_DB=todo_database
PGADMIN_DEFAULT_EMAIL=gin@example.com
PGADMIN_DEFAULT_PASSWORD=gintodopass
version: "3.8"
services:
postgres:
image: postgres:16-alpine
container_name: postgres
ports:
- 5433:5432
volumes:
- ./docker/postgres/init.d:/docker-entrypoint-initdb.d
- ./docker/postgres/pgdata:/var/lib/postgresql/data
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_INITDB_ARGS: "--encoding=UTF-8"
POSTGRES_DB: ${POSTGRES_DB}
hostname: postgres
restart: always
user: root
pgadmin:
image: dpage/pgadmin4
restart: always
ports:
- 81:80
environment:
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL}
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD}
volumes:
- ./docker/pgadmin:/var/lib/pgadmin
depends_on:
- postgres
docker-compose.ymlファイルの書き方に関してはまだまだ勉強中なので時に書くことはできないです実力不足ですみません笑
言及しておくこととしては、とりあえずdocker-compose.yml
の${~~~}
という記述の部分が.envを読み込んでいるということくらいです。
docker composeでサーバーを立ち上げる
それではいよいよサーバーを立ち上げてみます!DockerDesktopを立ち上げた状態で以下のコマンドを実行します。
docker compose up --build (-d)
-d
はつけてもつけなくても大丈夫です。このオプションをつけずに立ち上げるとログ?が表示されてつけないとログ?が表示されます。あれは果たしてログというのかどうかわからないです笑
2回目のdocker composeからは--buildは使用しなくて大丈夫です。
自分はインターンでも毎回
docker compose up -d
のみで使用しています。
--build
を含んだコマンドを実行すると少し時間がかかるかと思いますがコンテナが立ち上がります。(なんかこんな感じの画面がでてしばらく待つと完了します。)
[+] Running 24/13
⠇ postgres [⣿⣿⣿⣿⣿⣿⠀⣿⣿⣿⣿] 14.68MB / 97.73MB Pulling 21.9s
⠇ pgadmin [⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣿⣿⣿⠀⣿] 68.82MB / 160.9MB Pulling
これが完了したらルートディレクトリにdocker
というファイルが作成されているかと思います。これが作成されていることを確認したら、
にアクセスしてみましょう
ログイン画面では.env
で設定した情報を入力していきましょう。
Emailの欄にはtodo@example.com
、
Passwordの欄にはtodopass
を入力しましょう。
この画面になっていれば成功です!
この画面から中央のAdd New Serverをクリックしてフォームに入力していきます。
これらの情報を入力してこのような画面が出てきたら完了です!
マイグレーションのもとになるファイルを作成する
ここでは実際にデータベースに登録するデータの情報を登録していきます!
まずはルートディレクトリで以下のコマンドを実行します。
mkdir database
cd database
touch todotype.go
ここでは構造体を使用してデータの型を定義していきます。
Title
:文字列型
Description
:文字列型
Deadline
:時間型
とめちゃくちゃシンプルですが定義しておきます!
コードは以下の通りです。
package database
import (
"time"
"gorm.io/gorm"
)
type Todo struct {
gorm.Model
Title string
Description string
Deadline time.Time
}
ここでgorm.Model
って何だよってなりましたよね、自分もなりました。
中身を見てみると
package gorm
import "time"
// Model a basic GoLang struct which includes the following fields: ID, CreatedAt, UpdatedAt, DeletedAt
// It may be embedded into your model or you may build your own model without it
//
// type User struct {
// gorm.Model
// }
type Model struct {
ID uint `gorm:"primarykey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt DeletedAt `gorm:"index"`
}
こんな感じになってます。これはgormが準備してくれているもので勝手にプライマリーキーのID
と作成日時、更新日時、削除日時を更新してくれるというものなのですね。
ちなみにこの削除日時は初期値はnull
になっていてここに値(時間)が入力されたということが削除を示しています。完全に削除はしていなく、ソフトデリートなので復元可能な削除であることがわかりますね!
結局のところ、次のようなデータの構造体を定義していることになります。
package database
import (
"time"
"gorm.io/gorm"
)
type Todo struct {
ID uint `gorm:"primarykey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt DeletedAt `gorm:"index"`
Title string
Description string
Deadline time.Time
}
ちなみにgorm:"primarykey
この部分はメタデータとして読み込まれていてただの文字列として認識されるので補完などは効かないです。ここの情報に関しては今回は何も記述しません。データがnot null
とかnullable
などの基本的な制限から、リレーショナルデータベースの外部キーなどの設定もできます!
実際にマイグレーションを行う
.env
ファイルに以下のコードを追記します。
DB_HOST=localhost
DB_USER=todouser
DB_PASSWORD=todopass
DB_NAME=todo_database
DB_PORT=5433
ルートディレクトリに移動して以下のコマンドを実行します。
mkdir migration
cd migration
touch migration.go
まだ作成したファイルは触らないでおきます。
次に以下のコマンドを実行します。
cd ../database
touch database.go
コードの流れは後で追います。
作成したdatabase.go
を以下のように編集します。
package database
import (
"fmt"
"os"
"github.com/joho/godotenv"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
func Initialize() {
err := godotenv.Load()
if err != nil {
panic(".envファイルの情報が取得できません")
}
}
func DatabaseInit() *gorm.DB {
host := os.Getenv("DB_HOST")
user := os.Getenv("DB_USER")
password := os.Getenv("DB_PASSWORD")
dbname := os.Getenv("DB_NAME")
port := os.Getenv("DB_PORT")
if host == "" || user == "" || password == "" || dbname == "" || port == "" {
panic("必要な環境変数が設定されていません")
}
dbinfo := fmt.Sprintf(
"host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=Asia/Tokyo",
host,
user,
password,
dbname,
port,
)
db, err := gorm.Open(postgres.Open(dbinfo), &gorm.Config{})
if err != nil {
panic("データベースへの接続情報が異なっています")
}
return db
}
次に作成してあったmigration
ディレクトリのmigration.go
を編集します。
package main
import (
"log"
"todo-test/database"
)
func main() {
database.Initialize()
db := database.DatabaseInit()
err := db.AutoMigrate(&database.Todo{})
if err != nil {
panic("migration失敗!")
}
log.Println("migration成功!")
}
今回のマイグレーションではmigration.go
を実行します。
go run migration.go
を実行すると、まずdatabase.Initialize()
を呼び出します。この関数では.env
ファイルの情報を取得するための準備をしているイメージです。ここでもし準備ができなかったらpanic
で処理を中断するようにしています。
次に呼び出されるのはdatabase.DatabaseInit()
です。この関数では先ほど準備した.env
の内容を取得しています。そして仮にその情報がなければ処理を中断、全て取得できればデータベースへの接続に必要なデータをSprintf
でテキスト化して埋め込んでいます。それ以降は基本的にエラーハンドリングとデータベースへの接続の決まった書き方をしています。今回の関数の返り値の型は*gorm.DB
なのでdb
変数を返すようにしています。
そしてdb.AutoMigrate
関数を使用してデータベースのカラムの作成、カラムのデータの型の決定を行います。err := db.AutoMigrate(&database.Todo{})
ここで出てきた&database.Todo{}
が今回のデータベースの形を決めているということになります!データ型の情報を持たせた構造体のポインタを渡すだけでデータベースを作成してくれるのは非常に便利ですね!
これ以降のコードでもエラーハンドリングを行い、エラーがなければmigration成功!
というメッセージがターミナルに表示されるようになっています。
先ほどのコマンドを実行した結果として以下のような出力が得られます!
2024/11/13 23:30:27 migration成功!
これが出力されたら先ほどの
にアクセスしてみましょう。
左側のサイドバーを画像のように展開してきます。
todos
というところをクリックして、サイドバーの右上の表のような図を押してみると...
こんか感じでデータベースができているのがわかります!
しっかりデータの名前とデータの型が指定できているのがわかりますね!
今回の目的は達成できました!
最後に
今回初めてzennに記事を投稿してみました。よくアウトプットが大切だ!と言われる理由が分かったような気がします。これからも様々な学習したことを投稿していきます。そしてより強いエンジニアになります!
これからは簡単なAPIの開発に関しての記事などを書いてみたいと考えています。今回のTodoのデータに対してリレーショナルデータベースを導入してCRUD(Create, Read, Update, Delete)を実装していこうと思います。ここまで読んでくれてありがとうございました。何か不備などがあればご指摘ください。
Discussion