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

サーバーとクライアントの作成

gRPCサービスの定義が自動的に生成されるのは非常に素晴らしいことですが、それを具体的なgRPCサーバーに登録する必要があります。このサーバーは、あるTCPポートでトラフィックをリッスンし、RPCコールに応答することができます。

この部分は、異なるミドルウェアを接続するなど、チームや組織特有の動作が必要になるため、自動生成しないことにしました。 これは将来的に変更される可能性があります。 それまでの間、このセクションでは、サービス・コードをサーブするシンプルなgRPCサーバーを作成する方法を説明します。

サーバーの作成

新しいファイル cmd/server/main.go を作成し、次のように書き込みます:

package main

import (
"context"
"log"
"net"

_ "github.com/mattn/go-sqlite3"
"ent-grpc-example/ent"
"ent-grpc-example/ent/proto/entpb"
"google.golang.org/grpc"
)

func main() {
// entクライアントの初期化
client, err := ent.Open("sqlite3", "file:ent?mode=memory&cache=shared&_fk=1")
if err != nil {
log.Fatalf("failed opening connection to sqlite: %v", err)
}
defer client.Close()

// マイグレーションツールの実行(テーブルの作成など)
if err := client.Schema.Create(context.Background()); err != nil {
log.Fatalf("failed creating schema resources: %v", err)
}

// 生成されたUserサービスの初期化
svc := entpb.NewUserService(client)

// 新しいgRPCサーバの作成 (複数のサービスを1つのサーバーに接続できます)
server := grpc.NewServer()

// Userサービスをサーバーに登録します
entpb.RegisterUserServiceServer(server, svc)

// トラフィックをリッスンするために5000番ポートを開きます
lis, err := net.Listen("tcp", ":5000")
if err != nil {
log.Fatalf("failed listening: %s", err)
}

// トラフィックを無期限にリッスン
if err := server.Serve(lis); err != nil {
log.Fatalf("server ended: %s", err)
}
}

github.com/mattn/go-sqlite3のインポートを追加したことに注目してしてください。これをモジュールに追加する必要があります。

go get -u github.com/mattn/go-sqlite3

次に、サーバーを実行しながら、そのサーバーと通信するクライアントを書きましょう。

go run -mod=mod ./cmd/server

クライアントの作成

サーバーにいくつかの呼び出しを行うシンプルなクライアントを作ってみましょう。 cmd/client/main.goという名前の新規ファイルを作成し、以下を書き込みます:

package main

import (
"context"
"fmt"
"log"
"math/rand"
"time"

"ent-grpc-example/ent/proto/entpb"
"google.golang.org/grpc"
"google.golang.org/grpc/status"
)

func main() {
rand.Seed(time.Now().UnixNano())

// サーバーとのコネクションを開く
conn, err := grpc.Dial(":5000", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("failed connecting to server: %s", err)
}
defer conn.Close()

// Create a User service Client on the connection.
client := entpb.NewUserServiceClient(conn)

// サーバーにランダムなUserの作成を依頼する
ctx := context.Background()
user := randomUser()
created, err := client.Create(ctx, &entpb.CreateUserRequest{
User: user,
})
if err != nil {
se, _ := status.FromError(err)
log.Fatalf("failed creating user: status=%s message=%s", se.Code(), se.Message())
}
log.Printf("user created with id: %d", created.Id)

// 別のRPC呼び出しで、先に作成したUserを取得
get, err := client.Get(ctx, &entpb.GetUserRequest{
Id: created.Id,
})
if err != nil {
se, _ := status.FromError(err)
log.Fatalf("failed retrieving user: status=%s message=%s", se.Code(), se.Message())
}
log.Printf("retrieved user with id=%d: %v", get.Id, get)
}

func randomUser() *entpb.User {
return &entpb.User{
Name: fmt.Sprintf("user_%d", rand.Int()),
EmailAddress: fmt.Sprintf("user_%d@example.com", rand.Int()),
}
}

クライアントは、サーバーが待ち受けているポート5000への接続を作成し、新しいUserを作成するCreateリクエストを発行し、さらにデータベースからUserを取得する2回目のGetリクエストを発行します。 それでは、クライアントコードを実行してみましょう:

go run ./cmd/client

出力を観察する:

2021/03/18 10:42:58 user created with id: 1
2021/03/18 10:42:58 retrieved user with id=1: id:1 name:"user_730811260095307266" email_address:"user_7338662242574055998@example.com"

おめでとうございます! これで、本物のgRPCクライアントを作成し、本物のgRPCサーバーに接続することができました! 次のセクションでは、ent/gRPCインテグレーションが、より高度なentスキーマ定義をどのように扱うかを見ていきます。