Skip to main content

开发流程 - gRPC

👉 记录gRPC的开发流程,方便后续的开发

开发步骤#

1、生成proto模板文件#

eagle proto add api/like/v1/like.proto

内容如下

syntax = "proto3";
package api.like.v1;
option go_package = "github.com/go-microservice/moment-service/api/like/v1;v1";option java_multiple_files = true;option java_package = "api.like.v1";
service LikeService {    rpc CreateLike (CreateLikeRequest) returns (CreateLikeReply);    rpc UpdateLike (UpdateLikeRequest) returns (UpdateLikeReply);    rpc DeleteLike (DeleteLikeRequest) returns (DeleteLikeReply);    rpc GetLike (GetLikeRequest) returns (GetLikeReply);    rpc ListLike (ListLikeRequest) returns (ListLikeReply);}
message CreateLikeRequest {}message CreateLikeReply {}
message UpdateLikeRequest {}message UpdateLikeReply {}
message DeleteLikeRequest {}message DeleteLikeReply {}
message GetLikeRequest {}message GetLikeReply {}
message ListLikeRequest {}message ListLikeReply {}

2、定义proto#

主要是填充业务方法及message定义

vim api/like/v1/like.proto

3、生成pb文件#

业务rpc方法及message定义完,就可以生成pb文件了,具体操作如下

# 方式一:生成所有protomake grpc
# 方式二:生成指定proto的pb文件eagle proto client api/like/v1/like.proto
# Output# api/like/v1/like.pb.go #新增like.protolike_grpc.pb.go #新增

说明

protocol buffer编译器(protoc)生成的代码包含

  • 消息序列化代码(*.pb.go)
  • 客户端使用方法调用的远程接口存根(*_grpc.pb.go
  • 服务器代码实现的抽象接口(*_grpc.pb.go

4、生成server骨架代码#

实现了上一步生成的接口(*_grpc.pb.go 内)

# 生成骨架代码eagle proto server api/like/v1/like.proto
# 默认会输出到 internal/service# 如果需要指定到对应的目录,可以使用 -t 参数, eg: # eagle proto server -t internal/logic
# 查看internal/service/likeservice_grpc.go

5、注册服务到gRPC Server#

// NewGRPCServer creates a gRPC serverfunc NewGRPCServer(    cfg *app.ServerConfig,    // 新增    likeSvc *service.LikeServiceServer, ) *grpc.Server {
    grpcServer := grpc.NewServer(        grpc.Network("tcp"),        grpc.Address(":9090"),        grpc.Timeout(3*time.Second),    )
    // register biz service    // 新增    likev1.RegisterLikeServiceServer(grpcServer, likeSvc)
    return grpcServer}

6、在生成的server中编写业务逻辑#

这里编写具体的业务逻辑

# vim internal/service/likeservice_grpc.go
package service
import (    "context"
    pb "github.com/go-microservice/moment-service/api/like/v1")
var (    _ pb.LikeServiceServer = (*LikeServiceServer)(nil))
type LikeServiceServer struct {    pb.UnimplementedLikeServiceServer
    // here 导入你需要用到的repo    likeRepo repository.UserLikeRepo}
func NewLikeServiceServer() *LikeServiceServer {    return &LikeServiceServer{}}
func (s *LikeServiceServer) CreateLike(ctx context.Context, req *pb.CreateLikeRequest) (*pb.CreateLikeReply, error) {
    return &pb.CreateLikeReply{}, nil}func (s *LikeServiceServer) UpdateLike(ctx context.Context, req *pb.UpdateLikeRequest) (*pb.UpdateLikeReply, error) {    return &pb.UpdateLikeReply{}, nil}func (s *LikeServiceServer) DeleteLike(ctx context.Context, req *pb.DeleteLikeRequest) (*pb.DeleteLikeReply, error) {    return &pb.DeleteLikeReply{}, nil}func (s *LikeServiceServer) GetLike(ctx context.Context, req *pb.GetLikeRequest) (*pb.GetLikeReply, error) {    return &pb.GetLikeReply{}, nil}func (s *LikeServiceServer) ListLike(ctx context.Context, req *pb.ListLikeRequest) (*pb.ListLikeReply, error) {    return &pb.ListLikeReply{}, nil}

7、启动服务#

# 在根目录下运行go run main.go

8、调试接口#

调试工具,这里推荐使用 [grpcurl](https://github.com/fullstorydev/grpcurl)

# 查看服务列表grpcurl -plaintext localhost:9090 list
# Outputapi.like.v1.LikeServicegrpc.health.v1.Healthgrpc.reflection.v1alpha.ServerReflection
# 访问列表grpcurl -plaintext -d '{"user_id":2}' localhost:9090 api.like.v1.LikeService/ListLike

参数说明

  • -d 提交的参数, json格式
  • -plaintext 使用纯文本连接,跳过TLS

其他客户端调试工具

单元测试#

package service
import (    "context"    "log"    "net"    "testing"    "time"
    "google.golang.org/grpc"    "google.golang.org/grpc/test/bufconn"
    pb "github.com/go-microservice/user-service/api/user/v1")
const (    addr    = ""    bufSize = 1024 * 1024)
var (    lis *bufconn.Listener    srv *grpc.Server)
func initGRPCServer() {    lis = bufconn.Listen(bufSize)    srv = grpc.NewServer()
    pb.RegisterUserServiceServer(srv, &UserServiceServer{})
    go func() {        if err := srv.Serve(lis); err != nil {            log.Fatalf("srv.Serve, err: %v", err)        }    }()}
func TestUserServiceServer_GetUser(t *testing.T) {    initGRPCServer()    t.Cleanup(func() {        lis.Close()        srv.Stop()    })
    // test    dialer := func(context.Context, string) (net.Conn, error) {        return lis.Dial()    }
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)    t.Cleanup(func() {        cancel()    })
    conn, err := grpc.DialContext(ctx, addr, grpc.WithContextDialer(dialer), grpc.WithInsecure())    t.Cleanup(func() {        conn.Close()    })    if err != nil {        log.Fatalf("grpc.DialContext, err: %v", err)    }
    client := pb.NewUserServiceClient(conn)    resp, err := client.GetUser(context.Background(), &pb.GetUserRequest{Id: 1})    if err != nil {        t.Fatalf("client.GetUser, err: %v", err)    }
    if resp.User.Id != 1 {        t.Fatalf("Unexpected values: %v", resp.User)    }}

完整案例#

具体代码可以参看:用户服务

扩展阅读#

通过protoc 生成pb有两种方式,对于第二种方式,了解下即可,见到后知道是怎么回事。

最新方式(推荐)#

使用:google.golang.org/protobuf

$ protoc --go_out=. --go_opt=paths=source_relative \    --go-grpc_out=. --go-grpc_opt=paths=source_relative \    api/like/v1/like.proto

会生成两个文件 *.pb.go*._grpc.pb.go, 分别是消息序列化代码和 gRPC 代码.

较早方式(已废弃)#

使用:github.com/golang/protobuf

$ protoc -I . --go_out=plugins=grpc,paths=source_relative:. api/like/v1/like.proto

生成的 *.pb.go 包含消息序列化代码和 gRPC 代码.

FAQ#

Q1: github.com/golang/protobufgoogle.golang.org/protobuf 有什么区别?
A: github.com/golang/protobuf 模块是原始的 Go protocol buffer API。
google.golang.org/protobuf 模块是此 API 的更新版本,旨在简化、易用和安全,更新后的 API 的旗舰功能是支持反射以及将面向用户的 API 与底层实现分离。

Q2: 如果通过proto生成的结构体在访问http接口时,当返回值为零值的时候,json字段不显示如何处理?
A: 可以在message的字段中加入 gogoproto.jsontag, 示例如下:

import "gogo/protobuf/gogo.proto";
message ListPostReply {    repeated Post items = 1 [(gogoproto.jsontag) = "items"];    int64 count = 2 [(gogoproto.jsontag) = "count"];    bool has_more = 3 [(gogoproto.jsontag) = "has_more"];    string last_id = 4 [(gogoproto.jsontag) = "last_id"];}

Q3: 如何接收uri中或者form里的参数?
A: 可以使用 gogoproto.moretags,示例如下:

import "gogo/protobuf/gogo.proto";
// 接收uri中的参数message GetPostRequest {    string id = 1 [(gogoproto.moretags) = 'uri:"id"'];}
// 接收form里的参数message ListPostRequest {    int64 last_id = 1 [(gogoproto.moretags) = 'form:"last_id"'];    int32 limit = 2 [(gogoproto.moretags) = 'form:"limit"'];}

https://developers.google.com/protocol-buffers/docs/reference/go/faq

Reference#