使用一元或服务器流将切片从 grpc 服务器返回到客户端

问题描述 投票:0回答:1

以下代码是一个简单的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;
}
go grpc
1个回答
0
投票

一个很好表达的问题👍

你的方法和代码看起来不错。

虽然没有明确记录为“最佳实践”,但将业务逻辑类型(例如 User)与 Protobuf 生成的类型分开是“很好的实践”。这样做可以为您提供抽象的灵活性,尽管代价是必须编码|执行类型之间的映射。 我不熟悉

Gin
(!?),但是使用

proto.Message

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

消息流而不是离散块时,您会考虑流式传输(这会增加复杂性)。

    

© www.soinside.com 2019 - 2024. All rights reserved.