开发流程 - 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
其他客户端调试工具
- 类似curl的调试工具 https://github.com/fullstorydev/grpcurl
- 交互式的调试工具 https://github.com/ktr0731/evans
- UI调试工具 https://github.com/fullstorydev/grpcui
#
单元测试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
代码.
#
FAQQ1: github.com/golang/protobuf
和 google.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- https://grpc.io/docs/languages/go/quickstart/
- https://developers.google.com/protocol-buffers/docs/proto3
- https://grpc.io/docs/guides/error/
- https://github.com/avinassh/grpc-errors/blob/master/go/client.go
- https://stackoverflow.com/questions/64828054/differences-between-protoc-gen-go-and-protoc-gen-go-grpc
- https://jbrandhorst.com/post/grpc-errors/
- https://godoc.org/google.golang.org/genproto/googleapis/rpc/errdetails
- https://cloud.google.com/apis/design/errors
- https://github.com/grpc/grpc/blob/master/doc/health-checking.md
- https://eddycjy.com/posts/where-is-proto/
- https://stackoverflow.com/questions/52969205/how-to-assert-grpc-error-codes-client-side-in-go