メインコンテンツへスキップ

gRPC - connect - Render でwebサービスを作ってみる:サーバサイドCRUD環境構築

· loading · loading ·
kiitosu
著者
kiitosu
画像処理やデバイスドライバ、データ基盤構築からWebバックエンドまで、多様な領域に携わってきました。地図解析や地図アプリケーションの仕組みにも経験があり、幅広い技術を活かした開発に取り組んでいます。休日は草野球とランニングを楽しんでいます。
目次

はじめに
#

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.mdquery_todo.mdupdate_todo.mddelete_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というメッセージが気になります。これ本当にテストできているのかな?goExample関数はfmtで結果を表示し、期待値をコメントで定義することで結果を検証するそうです。今回はリンク先を日本語で読んでいたために、本来変換してはいけないところまで変換されてしまっていたようです。修正し再度試します。

-   // 出力します。
+	// Output:

実行しているテストを見られるように-vをつけました。

$ go test -v
=== RUN   ExampleTodo
--- PASS: ExampleTodo (0.00s)
PASS
ok      example 0.262s

今度こそテストが通りました!

テストコードの修正が必要だった理由は以下の通りです:

  • package名をプロジェクトの定義に合わせる必要があったため(todoexample
  • Example関数の命名規則に従う必要があったため(Example_TodoExampleTodo
  • 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でメソッド名がないため、正しい形式として認識される。 :::

まとめ
#

まだフィールドやエッジの定義をしていないのでなんのことかよく分かりませんが、とりあえずSQLitegoで使えるようになり正常に使えることが確認できたようです。

次は実際にフィールドやエッジを定義し、テーブルを使ってみます。

Reply by Email

関連記事

gRPC - connect - Render でwebサービスを作ってみる:server side connect
· loading · loading
gRPC - connect - Render でwebサービスを作ってみる:開発テンプレート完成
· loading · loading
gRPC - connect - Render でwebサービスを作ってみる:web service with connect
· loading · loading