PostgreSQL が採番した値を Go で扱おう
はじめに
エンジニア人生の本格的なアプリケーション開発の第一歩で永続化層を DynamoDB でスタートしたということがあったからなのかこれまでアプリケーション側でIDや時間を採番する手法をとってきました。そしてそれが楽だと思っていました。
しかし「データ指向アプリケーションデザイン」8章 分散システムの問題 を読んでそれはあまりよくないことなのかもしれないと気づきました。
じゃあ実際どうすればいいのか、をちょろっと手を動かして確認した記録です。
実装は Go です。
環境準備
uname -a
Darwin MacBook-Pro-7.local 22.6.0 Darwin Kernel Version 22.6.0: Wed Jul 5 22:22:52 PDT 2023; root:xnu-8796.141.3~6/RELEASE_ARM64_T8103 arm64
go version
go version go1.21.1 darwin/arm64
docker version
Client:
Cloud integration: v1.0.35-desktop+001
Version: 24.0.5
API version: 1.43
Go version: go1.20.6
Git commit: ced0996
Built: Fri Jul 21 20:32:30 2023
OS/Arch: darwin/arm64
Context: desktop-linux
Server: Docker Desktop 4.22.1 (118664)
Engine:
Version: 24.0.5
API version: 1.43 (minimum version 1.12)
Go version: go1.20.6
Git commit: a61e2b4
Built: Fri Jul 21 20:35:38 2023
OS/Arch: linux/arm64
Experimental: false
containerd:
Version: 1.6.21
GitCommit: 3dce8eb055cbb6872793272b4f20ed16117344f8
runc:
Version: 1.1.7
GitCommit: v1.1.7-0-g860f061
docker-init:
Version: 0.19.0
GitCommit: de40ad0
動作確認のための PostgreSQL は docker compose
で用意します。
comopse.yaml
services:
postgres:
container_name: postgres
image: postgres:15.4-alpine
ports:
- 5432:5432
environment:
TZ: UTC
LANG: ja_JP.UTF-8
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_INITDB_ARGS: "--encoding=UTF-8"
POSTGRES_HOST_AUTH_METHOD: trust
restart: always
以下のコマンドにて起動します。
docker compose --project-name postgres --file compose.yaml up -d
またテーブルは下記の SQL にて用意します。
ddl.sql
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE FUNCTION set_updated_at() RETURNS TRIGGER AS $$
BEGIN
IF (TG_OP = 'UPDATE') THEN
NEW.updated_at := now();
return NEW;
END IF;
END;
$$ LANGUAGE plpgsql;
CREATE TABLE tests (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
created_at TIMESTAMP (3) WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at TIMESTAMP (3) WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,
deleted BOOLEAN DEFAULT FALSE
);
CREATE OR REPLACE TRIGGER trg_tests_updated_at BEFORE UPDATE ON tests FOR EACH ROW EXECUTE PROCEDURE set_updated_at();
docker exec -i postgres psql -U postgres postgres < ddl.sql
これにて準備完了です。
実装
以下の main.go
にて実装しました。
package main
import (
"context"
"fmt"
"log/slog"
"os"
"time"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
)
type Test struct {
ID uuid.UUID
CreatedAt time.Time
UpdatedAt time.Time
Deleted bool
}
func main() {
conn, err := pgx.Connect(context.Background(), os.Getenv("DATABASE_URL"))
if err != nil {
panic(err)
}
defer func() {
if err := conn.Close(context.Background()); err != nil {
slog.Warn(err.Error())
}
}()
var res Test
ins := "INSERT INTO tests DEFAULT VALUES RETURNING id, created_at, updated_at, deleted"
if err := conn.QueryRow(context.Background(), ins).Scan(&res.ID, &res.CreatedAt, &res.UpdatedAt, &res.Deleted); err != nil {
panic(err)
}
slog.Info(fmt.Sprintf("INSERT: %+v", res))
upd := "UPDATE tests SET deleted = TRUE WHERE id = $1 RETURNING id, created_at, updated_at, deleted"
if err := conn.QueryRow(context.Background(), upd, res.ID).Scan(&res.ID, &res.CreatedAt, &res.UpdatedAt, &res.Deleted); err != nil {
panic(err)
}
slog.Info(fmt.Sprintf("UPDATE: %+v", res))
}
接続情報は環境変数にて実行時に渡します。
INSERT
実行時に DEFAULT VALUES
を指定してあげるとすべてのカラムをデフォルト値で保存( PostgreSQL での採番)ができるのですね。
INSERT
および UPDATE
実行時に RETURNING
にて実行結果を取得することができるのですね。
おわりに
PostgreSQL が採番した値はこんなにも簡単に扱えるのですね。
普段 PostgreSQL を使って開発をしていますがこんな単純なことも知らなかったのだと恥ずかしくなりました。一回ドキュメントを一通り読まないとダメですね。
( PostgreSQL の更新日時の自動更新ってトリガー関数を仕込むしか方法ないのですかね ... )
そして pgx
で実装してみましたが悪くないですね。
Go の ORM どうすればいいのだろう問題も pgx
でいいのかもしれません。
Discussion