🌟

Go + Bun + PostgreSQLの構成をつくる

2024/08/22に公開

背景

個人プロジェクトでPostgreSQLのクエリのために生SQLを用いているが、うっかりSQLインジェクションの脆弱性を生んだりしそうで怖いので、Bunを導入する。

とりあえず動くようにする

リポジトリの実装として与えていたdbを*sql.DBから*bun.DBに置き換える。
*sql.DBをラップするだけ。このとき、PostgreSQLを使うためには以下のようにpgdialect.New()を呼び出す。

        databaseUrl := fmt.Sprintf("postgresql://%s:5432/%s?user=%s&password=%s&sslmode=disable", dbHost, dbName, dbUser, dbPass)
-       db, err = sql.Open("pgx", databaseUrl)
+       sqldb, err := sql.Open("pgx", databaseUrl)
        if err != nil {
                return err
        }
	defer sqldb.Close()

+       db = bun.NewDB(sqldb, pgdialect.New())
+       defer db.Close()

 

参考: https://bun.uptrace.dev/postgres/

生SQLのプレースホルダを$1から?に変更する

func (r UserRepository) Find(ctx context.Context, id string) (*domain.User, error) {
-       const query = "SELECT id, display_name FROM users.users WHERE id = $1"
+       const query = "SELECT id, display_name FROM users.users WHERE id = ?"

	user := &domain.User{}
	err := r.db.QueryRowContext(ctx, query, id).Scan(&user.ID, &user.DisplayName)
	if err != nil {
		return nil, errors.Wrap(err, "scanning user")
	}
	return user, nil
}

SQLをクエリビルダで組み立てる

公式ドキュメントのGetting Startedに基づいて進めていく

  1. モデルを定義する
+type User struct {
+       bun.BaseModel `bun:"table:users.users,alias:u"`
+
+       ID          string `bun:"id,pk"`
+       DisplayName string `bun:"display_name,notnull"`
+}
+
  1. クエリを置き換える
 func (r UserRepository) Find(ctx context.Context, id string) (*domain.User, error) {
-       const query = "SELECT id, display_name FROM users.users WHERE id = ?"
+       user := new(User)
+       err := r.db.NewSelect().Model(user).Where("id = ?", id).Scan(ctx)
 
-       user := &domain.User{}
-       err := r.db.QueryRowContext(ctx, query, id).Scan(&user.ID, &user.DisplayName)
        if err != nil {
                return nil, errors.Wrap(err, "scanning user")
        }
-       return user, nil
+
+       return &domain.User{
+               ID:          user.ID,
+               DisplayName: user.DisplayName,
+       }, nil
 }

これで、問題なく生SQLを置き換えられた。

疑問

流石に構造体の詰め替えが多すぎる気がするが、これはよくあることなんだろうか。
現時点で、Userに対して以下のような構造体がある。

  • gRPCのリクエストの形式(UI)
  • ユースケース層のモデル(ユースケース)
  • ドメインモデル(ドメイン)
  • DBのモデル(リポジトリ))

この辺の疑問に答える本として手を動かしてわかるクリーンアーキテクチャ ヘキサゴナルアーキテクチャによるクリーンなアプリケーション開発
が紹介されていたが、まだ中身は見られていない。。。

Discussion