以下代码是一个简单的Golang gRPC Server,目前使用一元API。在我的服务 (GetUsers) 中,它返回一个类型
*[]User
,该类型与我的 protobuf *pb.GetUsersResponse
中定义的 GetUsersResponse 中的类型不同。由于它们有不同的类型,我运行了一个 for 循环来将数据从 *[]User
传输到 *pb.GetUsersResponse
,这有点像用额外的 for 循环执行类型转换的一种黑客方法。在我的客户端 grpc 代码(如下所示)中,我正在执行另一个 for 循环以将 JSON 响应发送回前端。有什么办法可以避免这种情况吗?我应该使用 grpc 一元还是服务器端流式传输?由于这是一个双 for 循环,服务器端流似乎将其减少为单个 for 循环。
func (s *usersGRPCServer) GetUsers(ctx context.Context, empty *empty.Empty) (*pb.GetUsersResponse, error) {
users, err := s.service.GetUsers()
if err != nil {
log.Println(err)
return nil, status.Error(codes.Internal, "Internal Server Error")
}
// Convert []*User to []*pb.User directly
var pbUsers []*pb.User
for _, user := range *users {
pbUsers = append(pbUsers, &pb.User{
FirstName: user.FirstName,
LastName: user.LastName,
Username: user.Username,
Email: user.Email,
Active: int64(user.Active),
Admin: int64(user.Admin),
})
}
return &pb.GetUsersResponse{
Users: pbUsers,
}, nil
}
// client grpc
func (app *application) grpcGetUsersHandler(urlString string) gin.HandlerFunc {
return func(c *gin.Context) {
grpcClient, _, err := newUsersGRPCClient(urlString)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"status": http.StatusInternalServerError, "message": "Internal Server Error"})
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
resp, err := grpcClient.GetUsers(ctx, &empty.Empty{})
if err != nil {
log.Println(err)
c.JSON(http.StatusInternalServerError, gin.H{"status": http.StatusInternalServerError, "message": "Internal Server Error"})
return
}
var usersData []map[string]interface{}
for _, user := range resp.Users {
userData := map[string]interface{}{
"first_name": user.FirstName,
"last_name": user.LastName,
"username": user.Username,
"email": user.Email,
"active": user.Active,
"admin": user.Admin,
}
usersData = append(usersData, userData)
}
c.JSON(http.StatusOK, gin.H{
"status": http.StatusOK,
"users": usersData,
})
}
}
这是我的 .proto 文件
syntax = "proto3";
option go_package = "./proto";
import "google/protobuf/empty.proto";
service UserService {
rpc GetUsers(google.protobuf.Empty) returns (GetUsersResponse);
}
message User {
string first_name = 1;
string last_name = 2;
string username = 3;
string email = 4;
int64 active = 5;
int64 admin = 6;
}
message GetUsersResponse {
repeated User users = 1;
}
一个很好表达的问题👍
你的方法和代码看起来不错。
虽然没有明确记录为“最佳实践”,但将业务逻辑类型(例如 User
)与 Protobuf 生成的类型分开是“很好的实践”。这样做可以为您提供抽象的灵活性,尽管代价是必须编码|执行类型之间的映射。
我不熟悉
Gin(!?),但是使用
protojson
转换为 JSON 的代码有可能的替代方案:
b, err := protojson.MarshalOptions{
UseProtoNames: true,
}.Marshal(resp)
if err != nil {
log.Fatalf("Failed to JSON marshal: %v", err)
}
// Ship the JSON to Gin here
log.Printf("Response:\n%s",string(b))
就您而言,您的 Protobuf 名称,例如last_name
与您的 JSON 字段名称匹配。默认情况下,
protojson
会将 Go 名称 (
LastName
) 驼峰式命名为 lastName
。解决方案是使用 MarshalOptions
和 UseProtoNames
。我不知道一元和流方法之间的切换是否会简化您的类型转换。我怀疑你不应该选择使用流式传输,因为它可能对类型转换有好处;相反,当您的客户端和服务器(及其后端)最自然地
GetUsers
作为
User
消息流而不是离散块时,您会考虑流式传输(这会增加复杂性)。