跳到主要内容

创建服务器与客户端

获得自动生成的 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 服务器(可将多个服务接入单个服务器)。
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()

// 在连接上创建 User 服务客户端。
client := entpb.NewUserServiceClient(conn)

// 请求服务器创建随机用户。
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 调用获取先前保存的用户。
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 的连接,随后发出 Create 请求创建新用户,再发出第二次 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 模式定义。