GORMで使うmodelの構造体をprivateにできるか

GORMで利用する構造体を下記のような形から

1type Hogehoge struct {
2	ID        int       `gorm:"id"`
3	Hoge      string    `gorm:"hoge"`
4	CreatedAt time.Time `gorm:"created_at"`
5	UpdatedAt time.Time `gorm:"updated_at"`
6}

次のような形にして、スコープを狭めることができないか調査しました。他の層で間違って参照されないように、スコープを狭めるのが狙いです。

1type hogehoge struct {
2	id        int       `gorm:"id"`
3	hoge      string    `gorm:"hoge"`
4	createdAt time.Time `gorm:"created_at"`
5	updatedAt time.Time `gorm:"updated_at"`
6}

結論

少し思っていたのとはちがいますが、下記のように定義することで、privateな構造体として定義しつつGORMでも利用できることがわかりました。

1type hogehoge struct {
2	ID        int       `gorm:"id"`
3	Hoge      string    `gorm:"hoge"`
4	CreatedAt time.Time `gorm:"created_at"`
5	UpdatedAt time.Time `gorm:"updated_at"`
6}

ご覧の通り、構造体の各フィールドのイニシャルは大文字にする必要があります。

構造体の各フィールドを大文字にしないといけない理由

GORMの処理で、各構造体がexportされているかをチェックする処理が有り、そこで引っかかってしまうためです。

https://github.com/go-gorm/gorm/blob/40f4afe8c21d96db63174bd501fb61d6e73c5587/schema/schema.go#L203C7-L203C7

1for i := 0; i < modelType.NumField(); i++ {
2	if fieldStruct := modelType.Field(i); ast.IsExported(fieldStruct.Name) {
3		if field := schema.ParseField(fieldStruct); field.EmbeddedSchema != nil {
4			schema.Fields = append(schema.Fields, field.EmbeddedSchema.Fields...)
5		} else {
6			schema.Fields = append(schema.Fields, field)
7		}
8	}
9}

GORMにParseWithSpecialTableName()という関数が用意されていて、そこで↑の処理がかかれています。構造体のフィールドの頭文字を小文字にしてしまうと、IsExported()でfalseが返ります。

IsExported()は次のような処理になっていました。

1// IsExported reports whether name starts with an upper-case letter.
2func IsExported(name string) bool { return token.IsExported(name) }

IsExported()でfalseになると、schema.Fieldsに値が入らないので、最終的に意図した処理ができなくなります。

まとめ

現在担当中のプロダクトをMVCモデルからレイヤードアーキテクチャに書き換えているのですが、その過程での調査です。今回は公式のドキュメントを読んでも分からなかったため、パッケージの中身をブレークポイント張ってデバッグしながら調査しました。良い勉強になりました。