【Go】repositoryパッケージ完全解説｜SQLインターフェース設計と実装ガイド

# 1. 【まずは全体コード】repositoryパッケージの中身

最初に、対象となる完全なコードを提示します。  
これが今回解説する対象です。

```go
package repository

import "context"

type SQL interface {
	ExecuteContext(context.Context, string, ...interface{}) error
	QueryContext(context.Context, string, ...interface{}) (Rows, error)
	QueryRowContext(context.Context, string, ...interface{}) Row
	BeginTx(ctx context.Context) (Tx, error)
}

type Rows interface {
	Scan(dest ...interface{}) error
	Next() bool
	Err() error
	Close() error
}

type Row interface {
	Scan(dest ...interface{}) error
}

type Tx interface {
	ExecuteContext(context.Context, string, ...interface{}) error
	QueryContext(context.Context, string, ...interface{}) (Rows, error)
	QueryRowContext(context.Context, string, ...interface{}) Row
	Commit() error
	Rollback() error
}
```

これから、このコードの各部分について**順番に**深堀りしていきます。


# 2. `repository.SQL`インターフェースの設計意図

まず、`SQL`インターフェースは、  
> **SQLデータベースを操作するための基本機能を標準化するためのもの**

です。

このインターフェースを満たす実装（たとえばPostgreSQL版）を用意すれば、  
アプリケーション側は**「PostgreSQLか？MySQLか？SQLiteか？」**を意識せずに使えます。


### 各メソッドの役割

| メソッド名 | 説明 |
|:---|:---|
| `ExecuteContext` | INSERT/UPDATE/DELETEを実行する（変更系クエリ） |
| `QueryContext` | SELECTで複数行の結果を取得する |
| `QueryRowContext` | SELECTで1行の結果を取得する |
| `BeginTx` | トランザクションを開始する |

→ つまり、**データベースを使うために絶対必要な最小限のセット**だけをまとめています。


# 3. `Rows`・`Row`・`Tx`の詳細解説

ここが少し取っつきづらいので、1つずつ順番に見ていきます。

## 3-1. `Rows`インターフェース

```go
type Rows interface {
	Scan(dest ...interface{}) error
	Next() bool
	Err() error
	Close() error
}
```

🔵 これは「SELECTで**たくさんの行**を取ったとき」に使うインターフェース。

- `Next()`で1行ずつ進む
- `Scan()`で今の行のデータを変数に取り込む
- 読み終わったら`Close()`
- エラー確認は`Err()`

**イメージ**
```go
rows, _ := db.QueryContext(ctx, "SELECT id, name FROM users")
defer rows.Close()

for rows.Next() {
    rows.Scan(&id, &name)
}
```

## 3-2. `Row`インターフェース

```go
type Row interface {
	Scan(dest ...interface{}) error
}
```

🔵 これは「SELECTで**1行だけ**取ったとき」に使うインターフェース。

- `Scan()`だけで完了
- `Next()`や`Close()`は不要

**イメージ**
```go
row := db.QueryRowContext(ctx, "SELECT id, name FROM users WHERE id = $1", 1)
row.Scan(&id, &name)
```

## 3-3. `Tx`インターフェース

```go
type Tx interface {
	ExecuteContext(context.Context, string, ...interface{}) error
	QueryContext(context.Context, string, ...interface{}) (Rows, error)
	QueryRowContext(context.Context, string, ...interface{}) Row
	Commit() error
	Rollback() error
}
```

🔵 これは「トランザクション中の操作」を定義するインターフェース。

- `ExecuteContext`でデータ変更
- `QueryContext`で複数行SELECT
- `QueryRowContext`で1行SELECT
- 最後に`Commit()`で確定 or `Rollback()`で取り消し

**イメージ**
```go
tx, _ := db.BeginTx(ctx)
defer tx.Rollback()

tx.ExecuteContext(ctx, "UPDATE accounts SET balance = balance - $1 WHERE id = $2", 100, 1)
tx.ExecuteContext(ctx, "UPDATE accounts SET balance = balance + $1 WHERE id = $2", 100, 2)

tx.Commit()
```

# 4. この設計のメリット

この設計には大きなメリットがいくつもあります。

| メリット | 説明 |
|:---|:---|
| データベース非依存 | PostgreSQL、MySQL、SQLite何でも対応できる |
| テストが楽 | モック実装を作ればDBなしでテストできる |
| クリーンアーキテクチャに合う | ドメイン層とデータ層をしっかり分離できる |
| 保守・拡張しやすい | 将来別DBに乗り換えても影響最小限 |


# 5. 【実例】PostgreSQL版の具体的な実装

ここからは、実際にPostgreSQL用にこのインターフェースを実装したコードを解説していきます。


## 5-1. 全体コード
```go
package database

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

	"github.com/gsabadini/go-clean-architecture/adapter/repository"

	_ "github.com/lib/pq"
)

type postgresHandler struct {
	db *sql.DB
}

func NewPostgresHandler(c *config) (*postgresHandler, error) {
	var ds = fmt.Sprintf(
		"host=%s port=%s user=%s dbname=%s sslmode=disable password=%s",
		c.host,
		c.port,
		c.user,
		c.database,
		c.password,
	)

	fmt.Println(ds)
	db, err := sql.Open(c.driver, ds)
	if err != nil {
		return &postgresHandler{}, err
	}

	err = db.Ping()
	if err != nil {
		log.Fatalln(err)
	}

	return &postgresHandler{db: db}, nil
}

func (p postgresHandler) BeginTx(ctx context.Context) (repository.Tx, error) {
	tx, err := p.db.BeginTx(ctx, &sql.TxOptions{})
	if err != nil {
		return postgresTx{}, err
	}

	return newPostgresTx(tx), nil
}

func (p postgresHandler) ExecuteContext(ctx context.Context, query string, args ...interface{}) error {
	_, err := p.db.ExecContext(ctx, query, args...)
	if err != nil {
		return err
	}

	return nil
}

func (p postgresHandler) QueryContext(ctx context.Context, query string, args ...interface{}) (repository.Rows, error) {
	rows, err := p.db.QueryContext(ctx, query, args...)
	if err != nil {
		return nil, err
	}

	row := newPostgresRows(rows)

	return row, nil
}

func (p postgresHandler) QueryRowContext(ctx context.Context, query string, args ...interface{}) repository.Row {
	row := p.db.QueryRowContext(ctx, query, args...)

	return newPostgresRow(row)
}

type postgresRow struct {
	row *sql.Row
}

func newPostgresRow(row *sql.Row) postgresRow {
	return postgresRow{row: row}
}

func (pr postgresRow) Scan(dest ...interface{}) error {
	if err := pr.row.Scan(dest...); err != nil {
		return err
	}

	return nil
}

type postgresRows struct {
	rows *sql.Rows
}

func newPostgresRows(rows *sql.Rows) postgresRows {
	return postgresRows{rows: rows}
}

func (pr postgresRows) Scan(dest ...interface{}) error {
	if err := pr.rows.Scan(dest...); err != nil {
		return err
	}

	return nil
}

func (pr postgresRows) Next() bool {
	return pr.rows.Next()
}

func (pr postgresRows) Err() error {
	return pr.rows.Err()
}

func (pr postgresRows) Close() error {
	return pr.rows.Close()
}

type postgresTx struct {
	tx *sql.Tx
}

func newPostgresTx(tx *sql.Tx) postgresTx {
	return postgresTx{tx: tx}
}

func (p postgresTx) ExecuteContext(ctx context.Context, query string, args ...interface{}) error {
	_, err := p.tx.ExecContext(ctx, query, args...)
	if err != nil {
		return err
	}

	return nil
}

func (p postgresTx) QueryContext(ctx context.Context, query string, args ...interface{}) (repository.Rows, error) {
	rows, err := p.tx.QueryContext(ctx, query, args...)
	if err != nil {
		return nil, err
	}

	row := newPostgresRows(rows)

	return row, nil
}

func (p postgresTx) QueryRowContext(ctx context.Context, query string, args ...interface{}) repository.Row {
	row := p.tx.QueryRowContext(ctx, query, args...)

	return newPostgresRow(row)
}

func (p postgresTx) Commit() error {
	return p.tx.Commit()
}

func (p postgresTx) Rollback() error {
	return p.tx.Rollback()
}
```

## 5-2. 実装ポイント解説

それぞれの構造体とメソッドがどのように役割を果たしているか、  
**実際のコードを引用しながら順番に解説**します。

### 🔵 `postgresHandler`（`SQL`インターフェース実装）

**この構造体は、通常のDB操作とトランザクション開始を担当します。**


📄 **ExecuteContext：変更系クエリを実行**
```go
func (p postgresHandler) ExecuteContext(ctx context.Context, query string, args ...interface{}) error {
	_, err := p.db.ExecContext(ctx, query, args...)
	return err
}
```
👉 `sql.DB.ExecContext`を呼んで、変更系クエリ（INSERT/UPDATE/DELETE）を実行します。


📄 **QueryContext：複数行SELECT**
```go
func (p postgresHandler) QueryContext(ctx context.Context, query string, args ...interface{}) (repository.Rows, error) {
	rows, err := p.db.QueryContext(ctx, query, args...)
	if err != nil {
		return nil, err
	}
	return newPostgresRows(rows), nil
}
```
👉 `sql.DB.QueryContext`でSELECTを実行し、結果を`postgresRows`にラップして返します。


📄 **QueryRowContext：1行SELECT**
```go
func (p postgresHandler) QueryRowContext(ctx context.Context, query string, args ...interface{}) repository.Row {
	row := p.db.QueryRowContext(ctx, query, args...)
	return newPostgresRow(row)
}
```
👉 `sql.DB.QueryRowContext`を使い、1行だけの結果を取得します。


📄 **BeginTx：トランザクション開始**
```go
func (p postgresHandler) BeginTx(ctx context.Context) (repository.Tx, error) {
	tx, err := p.db.BeginTx(ctx, &sql.TxOptions{})
	if err != nil {
		return postgresTx{}, err
	}
	return newPostgresTx(tx), nil
}
```
👉 新しいトランザクションを開始して、`postgresTx`として返します。


### 🔵 `postgresRow`（`Row`インターフェース実装）

**1行だけのSELECT結果を扱うためのラッパーです。**

📄 **Scan：取得した1行を構造体などにマッピング**
```go
type postgresRow struct {
	row *sql.Row
}

func (pr postgresRow) Scan(dest ...interface{}) error {
	return pr.row.Scan(dest...)
}
```
👉 `Scan()`を呼ぶだけで、結果を変数に取り込めます。

### 🔵 `postgresRows`（`Rows`インターフェース実装）

**複数行SELECTの結果を順に処理するためのラッパーです。**


📄 **Next/Scan/Err/Close：行ごとにデータを処理**
```go
type postgresRows struct {
	rows *sql.Rows
}

func (pr postgresRows) Scan(dest ...interface{}) error {
	return pr.rows.Scan(dest...)
}

func (pr postgresRows) Next() bool {
	return pr.rows.Next()
}

func (pr postgresRows) Err() error {
	return pr.rows.Err()
}

func (pr postgresRows) Close() error {
	return pr.rows.Close()
}
```
👉 `Next()`で次の行へ進み、`Scan()`でカラム値を取り出し、処理後は必ず`Close()`します。

### 🔵 `postgresTx`（`Tx`インターフェース実装）

**トランザクション内の一連のDB操作をラップします。**


📄 **ExecuteContext/QueryContext/QueryRowContext：トランザクション中のクエリ実行**
```go
type postgresTx struct {
	tx *sql.Tx
}

func (p postgresTx) ExecuteContext(ctx context.Context, query string, args ...interface{}) error {
	_, err := p.tx.ExecContext(ctx, query, args...)
	return err
}

func (p postgresTx) QueryContext(ctx context.Context, query string, args ...interface{}) (repository.Rows, error) {
	rows, err := p.tx.QueryContext(ctx, query, args...)
	if err != nil {
		return nil, err
	}
	return newPostgresRows(rows), nil
}

func (p postgresTx) QueryRowContext(ctx context.Context, query string, args ...interface{}) repository.Row {
	return newPostgresRow(p.tx.QueryRowContext(ctx, query, args...))
}
```
👉 通常のSQL操作とほぼ同じ感覚でトランザクション内でも操作できます。

📄 **Commit/Rollback：トランザクションの完了**
```go
func (p postgresTx) Commit() error {
	return p.tx.Commit()
}

func (p postgresTx) Rollback() error {
	return p.tx.Rollback()
}
```
👉 成功時は`Commit()`、失敗時は`Rollback()`で終了処理をします。


🧠【ここまでのまとめ】

| 実装対象 | 役割 |
|:---|:---|
| `postgresHandler` | 通常のSQL操作、トランザクション開始 |
| `postgresRow` | 1行SELECT結果をラップ |
| `postgresRows` | 複数行SELECT結果をラップ |
| `postgresTx` | トランザクション中の操作と管理 |

すべてのラッパーが、  
**repositoryパッケージのインターフェースに完璧に準拠**しています！


# 6. 総まとめ

この`repository`パッケージの設計によって、  
**SQL操作を超シンプル＆超柔軟に統一できる**世界が実現します。

- ✅ アプリケーション側は**データベースを意識しなくていい**
- ✅ **モック実装**を作れば**DBなしでテスト可能**
- ✅ 将来、**別DB（MySQLやSQLite）に移行しても最小限の変更**
- ✅ **クリーンなコード構成**を自然に維持できる