gormのメモ

## はじめに
gormはGo言語のORMライブラリーです。
gormで、最初見たとき、gormのcrud処理の前に、把握しておいた方がよかったことをメモりました。
・データベース接続箇所(シャーディングによるDBの振り分け例)
・`&gorm.Config{}` について
・structの定義箇所について
・`gorm.Model`の有無

## コード例

```go
package main

import (
	"fmt"

	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

// User モデル
type User struct {
	gorm.Model
	Name string
}

// シャードされたデータベースへの接続を管理する
var shardMap = map[int]*gorm.DB{}

func init() {
	// シャードデータベースの初期化
	for i := 1; i <= 2; i++ {
		db, err := gorm.Open(sqlite.Open(fmt.Sprintf("test_shard_%d.db", i)), &gorm.Config{})
		if err != nil {
			panic("failed to connect database")
		}
		db.AutoMigrate(&User{})
		shardMap[i] = db
	}
}

// ユーザーIDに基づいてシャードを選択
func getShard(userID uint) *gorm.DB {
	// ここでは単純化のため、IDが奇数の場合はシャード1、偶数の場合はシャード2を選択
	if userID%2 == 0 {
		return shardMap[2]
	}
	return shardMap[1]
}

func main() {
	// ユーザーの作成
	user1 := User{Name: "Alice"}
	user2 := User{Name: "Bob"}

	// ユーザーを適切なシャードに挿入
	shard1 := getShard(1)
	shard2 := getShard(2)

	shard1.Create(&user1)
	fmt.Println("Inserted User ID:", user1.ID)
	var user User
	shard1.First(&user, user1.ID) // 修正: ユーザー1の情報をシャード1から取得
	fmt.Println(user)

	shard2.Create(&user2)
	fmt.Println("Inserted User ID:", user2.ID)
	shard2.First(&user, user2.ID) // 修正: ユーザー2の情報をシャード2から取得
	fmt.Println(user)
}
```

## データベースへの接続

```go
db, err := gorm.Open(sqlite.Open(fmt.Sprintf("test_shard_%d.db", i)), &gorm.Config{})
```

この行は、GORMを使用してSQLiteデータベースに接続するためのコードです。具体的には、以下のステップに分けて解説できます。

### 1. データベースファイル名の生成
```go
fmt.Sprintf("test_shard_%d.db", i)
```
この部分は、データベースファイル名を動的に生成しています。`fmt.Sprintf` 関数を使用することで、文字列のフォーマット（この場合は `"test_shard_%d.db"`）に変数 `i` の値を埋め込みます。例えば、`i` が 1 なら `"test_shard_1.db"`、2 なら `"test_shard_2.db"` というファイル名が生成されます。これにより、異なるシャード（ここでは異なるデータベースファイル）に接続する準備をします。

### 2. SQLiteデータベースへの接続の開始
```go
sqlite.Open(fmt.Sprintf("test_shard_%d.db", i))
```
ここでは、ステップ1で生成されたファイル名を使ってSQLiteデータベースファイルを開くための指示を行っています。`sqlite.Open` 関数は、指定されたファイル名のSQLiteデータベースに接続するためのドライバを返します。

### 3. GORMを介したデータベース接続の設定と開始
```go
gorm.Open(sqlite.Open(fmt.Sprintf("test_shard_%d.db", i)), &gorm.Config{})
```
最終的に、`gorm.Open` 関数を使用して、ステップ2で得られたドライバ（つまり、特定のSQLiteデータベースへの接続情報）と、GORMの設定（`&gorm.Config{}`）をもとにデータベース接続を開始します。`&gorm.Config{}` は、GORMの動作をカスタマイズするための設定オブジェクトで、ここではデフォルト設定を使用しています。

この行を実行することで、変数 `db` にはデータベースへの接続ハンドルが格納され、`err` には接続時に発生したエラー（もし何か問題があれば）が格納されます。これにより、以降のコードで `db` を通じてデータベース操作を行うことができるようになります。


## `&gorm.Config{}` について
以下で使用しています。
```go
		db, err := gorm.Open(sqlite.Open(fmt.Sprintf("test_shard_%d.db", i)), &gorm.Config{})
```
`&gorm.Config{}` は、GORM でデータベース接続を初期化する際に、設定オプションを指定するために使用されます。この設定オプションを通じて、GORM の内部動作をカスタマイズすることができます。

GORM の `Open` 関数を使用してデータベースに接続する際には、第二引数として `&gorm.Config{}` を指定することが一般的です。この引数は、データベース接続時の設定をカスタマイズするために用います。もし特に設定を変更する必要がない場合でも、デフォルト設定を適用するために空の `&gorm.Config{}` を指定することが推奨されます。

`&gorm.Config{}` を明示的に指定することが、カスタマイズ可能性と明示性の観点から推奨されます。これにより、開発者はよりコントロールしやすい環境を得られ、将来的な設定変更やトラブルシューティングが容易になります。

### struct箇所
GORMでは、Goの構造体（Struct）を使用してデータベースのテーブルを表現します。この際、構造体のフィールド名は通常パスカルケース（またはキャメルケースの最初の文字が大文字）で記述されます。例えば、`UserID` や `FirstName` のようになります。

一方、データベースのテーブル名やカラム名には、スネークケースが一般的に使用されます。スネークケースでは、すべて小文字で単語の間をアンダースコア（`_`）で区切ります。例えば、`user_id` や `first_name` のようになります。

GORMはデフォルトで、Goの構造体のフィールド名からテーブルのカラム名を導き出す際に、パスカルケースからスネークケースへの変換を自動的に行います。つまり、構造体で定義された `UserID` フィールドはデータベースの `user_id` カラムに対応します。

カスタムのテーブル名やカラム名を使用したい場合は、GORMの `TableName` インターフェイスを実装するか、`column` タグを構造体フィールドに追加することで指定できます。例えば：

```go
type User struct {
  gorm.Model
  FirstName string `gorm:"column:custom_first_name"`
}

// カスタムテーブル名を指定
func (User) TableName() string {
  return "custom_users"
}
```


## `gorm.Model` について
以下で使用しています。
```go
type User struct {
	gorm.Model
	Name string
}
```

`gorm.Model` は、GORMが提供する組み込みの基本モデルです。これは、ほとんどのデータベーステーブルで共通して使用されるフィールドを含んでおり、次のようなフィールドが定義されています：

- ID: レコードのプライマリキー
- CreatedAt: レコードの作成日時
- UpdatedAt: レコードの最終更新日時
- DeletedAt: レコードの削除日時（ソフトデリートをサポート）

これらのフィールドを各モデルに自動的に追加することで、GORMはデータベース操作時にこれらの共通フィールドを利用できるようにします。例えば、レコードの作成日時や更新日時を自動的に管理することが可能です。

`gorm.Model` を含むモデル定義は次のようになります：

```go
type User struct {
    gorm.Model // 組み込みの基本モデルを埋め込み
    Name       string
}
```

このように、`gorm.Model` をモデルに埋め込むことで、開発者はこれらの共通フィールドを意識することなく、モデル固有のフィールドに集中してデータベース設計を行うことができます。これは、開発の手間を省き、一般的なデータベース操作を簡素化するための便利なショートカットです。

### 省略可
`gorm.Model` は便利なショートカットであり、多くのテーブルに共通する基本フィールド（`ID`, `CreatedAt`, `UpdatedAt`, `DeletedAt`）を自動的にモデルに追加します。しかし、これらのフィールドが必要ない場合や、よりカスタマイズされたフィールド名や挙動を望む場合は、`gorm.Model` をモデル定義から省略し、必要なフィールドを自分で定義することができます。

### `gorm.Model` を使用しない場合

`gorm.Model` を使用せずに、必要なフィールドを自分で定義する例を以下に示します：

```go
type User struct {
    ID        uint `gorm:"primaryKey"`
    CreatedAt time.Time
    UpdatedAt time.Time
    DeletedAt gorm.DeletedAt `gorm:"index"`
    Name      string
}
```

この例では、`ID`, `CreatedAt`, `UpdatedAt`, `DeletedAt` フィールドを自分で定義しています。`ID` フィールドには `primaryKey` タグを使用して、これがプライマリキーであることを GORM に伝えています。`DeletedAt` フィールドには `index` タグを使用しており、ソフトデリートの機能を利用するためにはこのフィールドのタイプを `gorm.DeletedAt` と指定する必要があります。

### カスタマイズの利点

- **柔軟性**: モデルの各フィールドを完全にコントロールできるため、データベースのスキーマとアプリケーションのモデルをより細かく調整することができます。
- **明確性**: `gorm.Model` を使用せずにフィールドを個別に定義することで、モデルの構造がより明確になり、新しい開発者がコードベースを理解しやすくなります。

### 注意点

- **手動での定義が必要**: `gorm.Model` を省略すると、その便利な機能（自動的なフィールド定義など）を手動で実装する必要があります。
- **ソフトデリートの管理**: ソフトデリートを使用する場合、`DeletedAt` フィールドを適切に定義し、そのタイプを `gorm.DeletedAt` として設定する必要があります。

結局のところ、`gorm.Model` の使用または非使用は、プロジェクトの要件と開発者の好みによって決まります。より多くのコントロールが必要な場合や、特定のフィールド名を使用したい場合は、`gorm.Model` を省略することが適切かもしれません。

### 切り出し
Go言語でのプロジェクト構成においては、モデル（またはエンティティ）定義を`model`フォルダ（または`models`フォルダ）に分離して管理することは一般的なアプローチの一つです。これにより、コードの整理が容易になり、モデル定義を他のコンポーネントから再利用しやすくなります。

具体的には、プロジェクトのルートディレクトリに`model`フォルダを作成し、その中に各テーブルに対応するGoのファイル（例えば`user.go`）を作成します。`user.go`ファイル内には、`User`構造体の定義を含めます。

```go
// model/user.go

package model

import (
    "time"

    "gorm.io/gorm"
)

type User struct {
    ID        uint `gorm:"primaryKey"`
    CreatedAt time.Time
    UpdatedAt time.Time
    DeletedAt gorm.DeletedAt `gorm:"index"`
    Name      string
}
```

そして、このモデルを他の場所（例えば、データベースとのインタラクションを扱うロジックなど）で使用するには、適切にインポートして利用します。

```go
package main

import (
    "fmt"
    "log"

    "your_project_path/model" // 適切にパスを設定してください
    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
)

func main() {
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    if err != nil {
        log.Fatalf("failed to connect database: %v", err)
    }

    // モデルの自動マイグレーション（テーブルがない場合は作成）
    db.AutoMigrate(&model.User{})

    // データの挿入
    db.Create(&model.User{Name: "John Doe"})

    // データの取得
    var user model.User
    db.First(&user, 1) // IDに基づいて取得
    fmt.Println(user)
}
```
このように、`model`フォルダを使ってモデル定義を管理することで、プロジェクトの構造を清潔に保ち、再利用性とメンテナンス性を高めることができます。モデルがアプリケーションの他の部分から独立しているため、将来的にデータベースのスキーマを変更する必要がある場合でも、変更が容易になります。

このように、GORMは柔軟にテーブル名やカラム名のカスタマイズをサポートしています。

## コード例の確認例
### SQLiteコマンドラインツールの起動
SQLiteでデータベースファイルを確認するためのコマンドを以下に示します。`sample2.go` スクリプトを実行した結果、`User` エンティティが2つの異なるシャードデータベース（`test_shard_1.db` と `test_shard_2.db`）に挿入されたようです。各データベースファイルに格納されているデータを確認するためには、それぞれに対して SQLite コマンドラインツールを使用します。


各シャードデータベースにアクセスするには、ターミナルから以下のコマンドを実行します。最初に `test_shard_1.db` を確認し、次に `test_shard_2.db` を確認します。

```bash
sqlite3 test_shard_1.db
```

これにより、`test_shard_1.db` データベースに対する SQLite コマンドラインインターフェースが開きます。

### テーブルの一覧表示

データベース内に存在するテーブルの一覧を表示するには、次のコマンドを使用します：

```sql
.tables
```

### データの確認

特定のテーブル（このケースでは `users` テーブル）の内容を確認するには、次のコマンドを実行します：

```sql
SELECT * FROM users;
```

### SQLiteコマンドラインツールの終了

作業が完了したら、SQLiteコマンドラインツールから退出するには、次のコマンドを使用します：

```sql
.quit
```

上記の手順で `test_shard_1.db` の内容を確認した後、同様の手順で `test_shard_2.db` に対しても行います：

```bash
sqlite3 test_shard_2.db
```

そして `.tables` および `SELECT * FROM users;` コマンドを使用して、`test_shard_2.db` 内のデータを確認します。

これらの手順を通じて、各シャードデータベースに格納されているデータを確認できます。`sample2.go` スクリプトの実行結果によると、各データベースには `ID` が 2 の `User` エンティティが存在するはずです（ただし、これは自動採番されたIDに依存しますので、実際のIDは実行時によって異なる可能性があります）。


## 参考記事
https://gorm.io/ja_JP/docs/index.html