はじめに #
gRPCとRemixを使ってサービスを公開したいと考えています。以下で開発のテンプレートができました。今日はサーバサイドでSQLiteのDBへのCRUDができるようにしてみたいと思います。
作業を進めるに当たりサーバー側にDBが欲しくなりました。まずはSQLiteみたいな簡易なDBはないかなということで探してみるとgo-sqlite3というものがあるんですね。
更に調べるとgo用のORM(Object-Relational Mapper)であるentというものがありました。entのスキーマ定義からproto定義を生成し、フロントエンドから直接CRUDもできるようです。また、graphqlもサポートされているようですので、FEから自由にデータを取得したいときには便利ですね。ORMを使うことで型安全性を保ったデータベースの操作が簡単にできますね。
以下のリポジトリをベースに作業を進めます。
変更後の結果は以下です
参照情報 #
今回は以下の記事を参照してentの導入をしてみたいと思います。
インストール #
作業ディレクトリに移動します
cd ./backend
entをインストールします
go get entgo.io/ent/cmd/ent
コード生成 #
スキーマ作成 #
サンプルとして Todo スキーマを作成します
go run -mod=mod entgo.io/ent/cmd/ent new Todo
以下のようにコードが追加されました。
追加されたtodo.goは以下の内容となっていてTodoのスキーマが定義されていますが、フィールドやエッジが定義されていませんので後で追加していきます。
package schema
import "entgo.io/ent"
// Todoは、Todoエンティティのスキーマ定義を保持します。
type Todo struct {
ent.Schema
}
// Todoのフィールド
func (Todo) Fields() []ent.Field {
return nil
}
// Todoのエッジ
func (Todo) Edges() []ent.Edge {
return nil
}
:::message フィールドというのはいわゆるDBの列定義ですね。エッジというのは初めて効いたのですが、entでいうリレーションシップのことだそうです。 :::
コマンド生成 #
上記でスキーマを作りましたが、これだけだとデータの操作ができません。CRUD操作を行うコードを生成します。
go generate ./ent
何やらいっぱいファイルが作られました。todo.goには型定義のようなものもあります。create_todo.md、query_todo.md、update_todo.md、delete_todo.mdがあるので、ここにORMの実装がありそうですね。
テストケース生成 #
go-sqlite3をインストールし、SQLiteを使ったテストケースを生成します。
go get github.com/mattn/go-sqlite3
touch example_test.go
以下をexample_test.goに貼り付けます。
package todo
import (
"context"
"log"
"todo/ent"
"entgo.io/ent/dialect"
_ "github.com/mattn/go-sqlite3"
)
func Example_Todo() {
// インメモリーのSQLiteデータベースを持つent.Clientを作成します。
client, err := ent.Open(dialect.SQLite, "file:ent?mode=memory&cache=shared&_fk=1")
if err != nil {
log.Fatalf("failed opening connection to sqlite: %v", err)
}
defer client.Close()
ctx := context.Background()
// 自動マイグレーションツールを実行して、すべてのスキーマリソースを作成します。
if err := client.Schema.Create(ctx); err != nil {
log.Fatalf("failed creating schema resources: %v", err)
}
// 出力します。
}
テストを実行します。
go test
失敗します。。。むむむ。。。
今回既存のpackageexampleが定義されたプロジェクトに追加したので、テストコードもプロジェクトのgo.modで定義されているpackage名に合わせる必要がありました。また、テスト名は何やら命名規則があるそうです(後述)。
-package todo
+package example // go.modで定義しているpackageにあわせる
import (
"context"
"log"
- "todo/ent"
+ "example/ent" // package名を修正
"entgo.io/ent/dialect"
_ "github.com/mattn/go-sqlite3"
)
-func Example_Todo() {
+func ExampleTodo() {
通りました!
$ go test
testing: warning: no tests to run
PASS
ok example 0.264s
しかしtesting: warning: no tests to runというメッセージが気になります。これ本当にテストできているのかな?goのExample関数はfmtで結果を表示し、期待値をコメントで定義することで結果を検証するそうです。今回はリンク先を日本語で読んでいたために、本来変換してはいけないところまで変換されてしまっていたようです。修正し再度試します。
- // 出力します。
+ // Output:
実行しているテストを見られるように-vをつけました。
$ go test -v
=== RUN ExampleTodo
--- PASS: ExampleTodo (0.00s)
PASS
ok example 0.262s
今度こそテストが通りました!
テストコードの修正が必要だった理由は以下の通りです:
- package名をプロジェクトの定義に合わせる必要があったため(
todo→example) - Example関数の命名規則に従う必要があったため(
Example_Todo→ExampleTodo) Output:コメントを正しく記述する必要があったため
:::message
Output:について
今回の例ではfmt.PlintlnがなかったのでOutput:だけで良かったですが、fmt.Printlnする場合は以下のようになります
fmt.Println("test")
// Output:
// test
:::
:::message テスト命名規則。Example_TodoがNGで、ExampleTodoがOKな理由 by chatGPT
Example_TodoがNGな理由 #
Example_Todoという名前は、アンダースコア(_)が型名とメソッド名を区切るために使われるというGoの命名規則に従っているように見えます。しかし、この場合、Todoという型に対してメソッド名が指定されていないため、Goのテストフレームワークは「型名とメソッド名の区切りがあるのにメソッド名がない」という状態を「malformed(不正な形式)」と判断します。
つまり、Example_Todoは以下のように解釈されます:
- 型名:
""(空) - メソッド名:
Todo
これは型名が空であるため、不正な形式と判断されます。
Example_TodoがOKな理由 #
一方、ExampleTodoは以下のように解釈されます:
- 型名:
Todo - メソッド名:なし(型そのものの例)
これはGoの命名規則に完全に合致しているため、正常に動作します。
まとめ #
Example_Todo:型名が空でメソッド名がTodoとなり、不正な形式と判断される。ExampleTodo:型名がTodoでメソッド名がないため、正しい形式として認識される。 :::
まとめ #
まだフィールドやエッジの定義をしていないのでなんのことかよく分かりませんが、とりあえずSQLiteがgoで使えるようになり正常に使えることが確認できたようです。
次は実際にフィールドやエッジを定義し、テーブルを使ってみます。
Reply by Email