【初LT登壇】「sqlxからpgxへの移行を今はしない判断をするまで」の深堀りをしてみます！

こんにちは。

株式会社HRBrainでバックエンドエンジニアをしているみつです！

この春に新卒として入社し、さまざまな経験を積む中で、「**sqlライブラリの移行**」というタスクに立ち向かう機会がありました。

[**2024.05.20にししとうLT**](https://4410.connpass.com/event/316005/)では、時間の都合上話せなかった詳細について、この記事で補足したいと思います:relaxed:

（ちなみに、ししとうLTは私にとって人生初のLT登壇でした:joy: 記事執筆時点では、社外LTに聞く側でしか参加したことがないので、もし誘っていただけたら嬉しいですｗ）

- [資料はこちら ▼](#資料はこちら-)
- [`sqlx`から`pgx`への移行までの話](#sqlxからpgxへの移行までの話)
- [出したかったログの種類:writing\_hand:](#出したかったログの種類writing_hand)
- [色々なGoのSQLライブラリを検討:sunny:](#色々なgoのsqlライブラリを検討sunny)
  - [`pq`について](#pqについて)
  - [`sqlx`について](#sqlxについて)
  - [`pgx`について](#pgxについて)
  - [`sqlc`について](#sqlcについて)
- [ログの導入の前に大変なことが発覚](#ログの導入の前に大変なことが発覚)
- [Query Insightsの導入をしつつ、ライブラリ移行は見送りに](#query-insightsの導入をしつつライブラリ移行は見送りに)
- [まとめ](#まとめ)
- [PR](#pr)

## 資料はこちら ▼
https://speakerdeck.com/mitsu0428/sqlxkarapgxhenoyi-xing-wojin-hasinaipan-duan-wosurumade

## `sqlx`から`pgx`への移行までの話

新規プロダクト開発を進める中で、「**リリースまでにログを出したい！**」というタスクがありました。

せっかくなのでやってみたい！と手を挙げ、チームで使用していた`sqlx`にloggerを持たせる方法を検討しました。

しかし、`sqlx`ではloggerを持たせることができないという壁にぶつかりました。

加えて、同じ時期にデータベースの設計見直しが行われており、この機会にライブラリも見直す必要があるのではないかと考えるようになり、"ぷち沼"にハマっていくことになります。

## 出したかったログの種類:writing_hand:
出したいログの種類は、大きく3つでした。

- **アクセスログ**（いつアクセスしたかなど）
- **アプリケーションログ**（エラーのスタックトレースなど）
- **SQL操作ログ**（Cloud SQLでどんなSQLが実行されたかなど）

## 色々なGoのSQLライブラリを検討:sunny:

まず、GoにはどんなSQLライブラリがあり、どのライブラリであればLoggerを持たせられるのかを検討しました。

標準パッケージである`database/sql`から最近流行りの`sqlc`（？）まで、一言にSQLと言ってもさまざまな種類のライブラリがあることを知りました。

正直なところ、種類が多くて、それぞれが何に使われるものなのかよく分からなかったので、一つずつドキュメントを読んで知識を深めることにしました。笑

- **database/sql**
- **pq**
- **sqlx**
- **pgx**
- **sqlc**

...etc.

### `pq`について

- `pq`パッケージは、`database/sql`パッケージ用の純粋なGo PostgreSQLドライバです。[DeepL訳]
- ほとんどの場合、クライアントはこのパッケージを直接使う代わりに `database/sql`パッケージを使うでしょう。[DeepL訳]

理解したのは、純粋なGoのPostgreSQLドライバとあるように、`database/sql`と合わせて使うことで機能が拡張されるライブラリ。

`database/sql`でPostgreSQLにある機能を使うためにこのドライバを使うことができます。

```go
import (
	"database/sql"

	_ "github.com/lib/pq"
)

func main() {
	connStr := "user=pqgotest dbname=pqgotest sslmode=verify-full"
	db, err := sql.Open("postgres", connStr)
	if err != nil {
		log.Fatal(err)
	}

	age := 21
	rows, err := db.Query("SELECT name FROM users WHERE age = $1", age)
	…
}
```

https://pkg.go.dev/github.com/lib/pq#section-readme

### `sqlx`について
- `sqlx`は、Goの標準ライブラリ `database/sql` を拡張するためのライブラリである。[DeepL訳]

これも`pq`同様、`database/sql`と合わせて使うことで拡張することが出来るライブラリのようです。（`pq` ≒ `sqlx`）

```go
package main

import (
    "database/sql"
    "fmt"
    "log"

    _ "github.com/lib/pq"
    "github.com/jmoiron/sqlx"
)
```

https://pkg.go.dev/github.com/jmoiron/sqlx#section-readme

### `pgx`について
- pgxはPostgreSQL用の純粋なGoドライバとツールキットです。[DeepL訳]
- pgx ドライバは低レベルで高性能なインタフェースであり、`LISTEN` / `NOTIFY` や `COPY` といった PostgreSQL 固有の機能を提供します。また、標準の `database/sql` インタフェースのアダプタも含まれています。[DeepL訳]

最初にドキュメントを読んだ時は、`pq`や`sqlx`より強そうという印象を受けました。

このライブラリは、とても便利でpgxをドライバとして使用することもできたり、`database/sql`のアダプタも含まれているとあるのでその機能を担うことも出来たりします。

実装例でも、`database/sql`のimportなしに使っています。

```go
package main

import (
	"context"
	"fmt"
	"os"

	"github.com/jackc/pgx/v5"
)

func main() {
	// urlExample := "postgres://username:password@localhost:5432/database_name"
	conn, err := pgx.Connect(context.Background(), os.Getenv("DATABASE_URL"))
	if err != nil {
		fmt.Fprintf(os.Stderr, "Unable to connect to database: %v\n", err)
		os.Exit(1)
	}
	defer conn.Close(context.Background())

	var name string
	var weight int64
	err = conn.QueryRow(context.Background(), "select name, weight from widgets where id=$1", 42).Scan(&name, &weight)
	if err != nil {
		fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
		os.Exit(1)
	}

	fmt.Println(name, weight)
}
```
https://pkg.go.dev/github.com/jackc/pgx/v5

### `sqlc`について
- 普通にORM
- sqlcはSQLから**完全に型安全なイディオムGoコード**を生成します。[DeepL訳]

これは調べて見てすぐ分かったことですが、必須のライブラリではありません。

オブジェクト関係マッピング（ORM、Object-Relational Mapping）というSQLを直接触ること無く記述するためのライブラリでした。

他にも有名どころだと、`GORM`や`SQLBoiler`など様々なものが存在していてそれぞれにメリット・デメリットがあるようです。

https://docs.sqlc.dev/en/latest/

## ログの導入の前に大変なことが発覚

様々なライブラリを調査し、選定を終え、ログ導入の準備が整いつつあったのですが…

「ちょっと待てよ。**DBの設計見直しが入ったということは、APIが全て置き換わるということなんじゃないか**」という議論が持ち上がりました。

実際にその通りで、DB設計の見直しが行われているため、ライブラリ導入でミスをしても影響は少ない（=今が導入しやすいタイミングと言える）一方で、DBが変更され、APIスキーマも変更され、実装も大きく方向転換することになります。

つまり、今頑張って書き換えや置き換え作業を行っても、膨大な時間ロスが発生するのではないかという議論も生まれました。

## Query Insightsの導入をしつつ、ライブラリ移行は見送りに
そもそも、

- Cloud SQLを利用する中でどのようなSQLが実行されているのか
- クエリのパフォーマンスが明らかに悪い時に確認したい

などが背景で始まったログ導入タスク。

Query Insightsで一時的に必要なものな情報を取れることが判明し、それらの設定することでログの対応はひとまず見送ることになりました。

Query Insightsについては別記事でも紹介してるのでぜひ見てください:relaxed:

https://qiita.com/mi2__user/items/dd66d3e7b3b613ec53d7


## まとめ

インターン時代は既存サービスのフロントエンドを主に実装していましたが、新卒からは新規プロダクトでのバックエンド開発を担当しています。

その中で、ライブラリの導入は、初期段階だからこそ経験できる、楽しい壁だと感じています！

今回のライブラリ移行を通して、以下のようなことを学びました。

- Goでデータベースを操作する際は、`database/sql`を使うのが基本であること
- しかし、`database/sql`はPostgreSQL以外のデータベース（MySQLなど）にも対応しているため、PostgreSQLの機能を利用する場合は、拡張ライブラリが必要になること:frowning2:
    - 例）↑ を埋めるのが、`pgx`, `pq`, `sqlx`だったりする
    - 例）↑ `database/sql` + PostgreSQL独自機能 = `pgx`

- `pq`は、`database/sql`をより使いやすく、PostgreSQL風に設計されたライブラリ
- `sqlx`も、`database/sql`をより使いやすくしたり、`database/sql`ではできない機能を提供するライブラリ
    - （ちなみに、機能的に重複しているため、`sqlx`と`pgx`はあまり併用しないようです。）

現在は、ライブラリの入れ替えはほとんど行われていませんが、チームではアーキテクチャ周りの議論でいつも大盛りあがり（？）です！

また明日からも楽しくコーディングを続けていきたいと思います！:v:

## PR
株式会社HRBrainでは、一緒に働く仲間を募集しています！

興味を持っていただいた方はぜひ弊社の採用ページをご確認ください！

HRBrain文化を一緒に作っていきましょう！

https://www.hrbrain.co.jp/recruit/