建模服务错误 – gRPC与Google API

huangapple go评论77阅读模式
英文:

Modelling service errors - gRPC Vs Google API

问题

在gRPC服务中处理错误通常需要使用status消息和错误codes。这两者都有两个定义:

  • Google API定义(googleapis/go-genproto)- 用于常见协议缓冲区类型的生成Go包,以及用于Google的gRPC API的生成gRPC代码
  • gRPC定义(grpc/grpc-go)- gRPC的Go实现

这两个定义的StatusCodes的Go包如下:

Google API

gRPC

由于我是自己的gRPC服务的客户端,而不是现有Google gRPC API的客户端,我想使用StatusCodegRPC定义。

然而,StatusgRPC proto文件实际上是从Google API定义中复制的。请参见https://github.com/grpc/grpc/tree/master/src/proto/grpc/status。status.protogo_package也没有改变,因此Google API和gRPC定义都使用以下Go包:

option go_package = "google.golang.org/genproto/googleapis/rpc/status;status";

结果是在定义API时,使用Status的唯一方法是通过以下方式导入它:

import "google/rpc/status.proto";

...并在Go中使用语言绑定导入:

import (
   "google.golang.org/genproto/googleapis/rpc/status"
)
// Go服务器代码...

但正如前面所述,这是错误的,因为我不是Google API的客户端,而是自己的gRPC服务的客户端。因此,应该使用以下方式导入语言绑定:

import (
   "google.golang.org/grpc/status"
)
// Go服务器代码...

正如预期的那样,如果我切换到导入gRPC语言绑定并尝试向API客户端返回Status消息,我会得到一个编译错误:

cannot use &(status.Status literal)
(value of type *"google.golang.org/grpc/internal/status".Status) as 
*"google.golang.org/genproto/googleapis/rpc/status".Status value

这是由于我的.proto文件使用了Google API定义的Status,而服务器实现(在Go中)使用了gRPC定义。

这个问题也影响到错误代码,因为Google API使用有符号32位整数(int32),而gRPC使用无符号32位整数(uint32)。

问题

  • 我的断言,即我应该使用gRPC定义的StatusCodes,是正确的吗?

  • 如果我的断言是正确的,那么当它被打包为Google API时,我如何使用gRPC定义的Status

英文:

Handling errors in a gRPC service commonly requires both a status message and error codes. Both have two definitions:

  • Google APIs definition (googleapis/go-genproto) - the generated Go packages for common protocol buffer types, and the generated gRPC code for Google's gRPC APIs
  • gRPC definition (grpc/grpc-go) - the Go implementation of gRPC

The Go packages for both definitions of Status and Codes are:

Google APIs

gRPC

Since I'm a client of my own gRPC service and not a client of an existing Google gRPC API, I want to use the gRPC definitions of Status and Code.

However, the gRPC proto file for Status is actually copied from Google APIs definition. See https://github.com/grpc/grpc/tree/master/src/proto/grpc/status. The go_package of status.proto is also unchanged, so both the Google API and gRPC definitions use the following Go package

option go_package = "google.golang.org/genproto/googleapis/rpc/status;status";

The upshot is the only way to use Status when defining an API is by importing it with

import "google/rpc/status.proto";

...and importing the language bindings in Go with

import (
   "google.golang.org/genproto/googleapis/rpc/status"
)
// Go server code...

But as stated earlier, this is wrong since I'm not a client of a Google API, but rather my own gRPC service. Therefore the language bindings should be imported with

import (
   "google.golang.org/grpc/status"
)
// Go server code...

As expected if I switch to importing the gRPC language bindings and try and return a Status message to the API client, I get a compile error

cannot use &(status.Status literal)
(value of type *"google.golang.org/grpc/internal/status".Status) as 
*"google.golang.org/genproto/googleapis/rpc/status".Status value

This is caused by my .proto file using the Google API definition of Status while the server implementation (in Go) uses the gRPC definition.

The problem impacts error codes since Google APIs uses signed 32 bit integers (int32) whereas gRPC uses unsigned 32 bit integers (uint32).

Questions

  • Is my assertion that I should be using the gRPC definition of Status and Codes correct?

  • If my assertion is correct, how can I use the gRPC definition of Status when it's packaged for Google APIs?

答案1

得分: 1

我们需要区分几种情况。其中一些是显而易见的,一些则不是。

仅从 gRPC 处理程序返回 Status

如果你的 proto schema(.proto 文件)没有直接定义使用 StatusCode 的消息,那么 gRPC 处理程序可以通过 "google.golang.org/grpc/status".Error()Newf().Err() 来满足返回类型为 error,就这样。

示例:

// 实现 SomeServiceServer 的一元 RPC GetFoo
func (s *SomeService) GetFoo(ctx context.Context, req *grpc.FooRequest) (*grpc.FooResponse, error) {
    // status 是 "google.golang.org/grpc/status"
	return nil, status.Error(codes.Unimplemented, "coming soon")
}

在你的 .proto 文件中使用 Status

在这种情况下,你必须使用 googleapis 实现。正如你已经看到的,status.proto 的 Go 包被定义为:

option go_package = "google.golang.org/genproto/googleapis/rpc/status;status";

假设你有以下的 .proto 文件,其中导入的 status.proto 只是 gRPC status.proto 的复制粘贴,参考了 这个问题

syntax = "proto3";

package test;

import "status.proto";

option go_package = ".;main";

message Foo {
  string a = 1;
  google.rpc.Status status = 2;
}

目录结构如下:

/protos
|_ status.proto
|_ test.proto

使用以下命令编译上述文件:

cd protos && protoc -I=. --go_out=. test.proto

然后生成的 Go 代码将有以下导入:

import (
    status "google.golang.org/genproto/googleapis/rpc/status"
)

你必须通过 go get google.golang.org/genproto 来满足这个导入。

所以关于你的第一个问题,你只能在 proto 文件中使用 googleapisStatus,因为这是 status.proto 声明其 Go 包的方式。

在 Go 代码中使用生成的 googleapis Status

由于导入的 Go 包来自 googleapis,所以你必须在你的 Go 代码中使用它来初始化这样的消息:

package main

import (
	"fmt"
	googleapis "google.golang.org/genproto/googleapis/rpc/status"
)

func main() {
	foo := &Foo{
		A: "foo",
		Status: &googleapis.Status{
			Code:    int32(code.Code_OK),
			Message: "all good",
		},
	}
	// fmt.Println(foo)
}

是的,但我 必须 在我的 Go 代码中使用 grpc-goStatus

你不能。protoc 生成的代码使用了上述描述的包。如果你绝对需要使用 grpc-go 构造这些 Status 字段,你可以使用 Status.Proto

package main

import (
	"fmt"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

func main() {
	foo := &Foo{
		A: "foo",
		Status: status.New(codes.OK, "all good").Proto(),
	}
	fmt.Println(foo)
}

只是为了记录,使用 status.FromProto 也可以实现相反的功能:

package main

import (
	"fmt"
	googleapis_codes "google.golang.org/genproto/googleapis/rpc/code"
	googleapis "google.golang.org/genproto/googleapis/rpc/status"
	"google.golang.org/grpc/status"
)

func main() {
	gapisStatus := &googleapis.Status{
		Code: int32(googleapis_codes.Code_OK),
		Message: "all good",
	}
	grpcStatus := status.FromProto(gapisStatus)
	fmt.Println(grpcStatus)
}

作为一个行为不良的替代方案,你可以将 status.proto 源代码复制粘贴到你的项目中,并手动更改 go_package

option go_package = "google.golang.org/grpc/status;status";

这样,protoc 将使用这个导入生成 Go 代码,你自己的源代码也可以遵循相同的方式。当然,这意味着你现在有了自己的 status.proto 分支。

英文:

We need to distinguish a few cases. Some of them are obvious, some are not.

Just returning Status from a gRPC handler

If your proto schema (.proto files) doesn't define messages that use Status or Code directly, then the gRPC handlers can satisfy the return type error simply with "google.golang.org/grpc/status".Error(), or Newf().Err(). And that's about it.

Example:

// implements SomeServiceServer unary RPC GetFoo
func (s *SomeService) GetFoo(ctx context.Context, req *grpc.FooRequest) (*grpc.FooResponse, error) {
    // status is "google.golang.org/grpc/status"
	return nil, status.Error(codes.Unimplemented, "coming soon")

Using Status in your .proto files

In this case, you are forced to use the googleapis implementation. As you already have seen, the status.proto Go package is defined as:

option go_package = "google.golang.org/genproto/googleapis/rpc/status;status";

So let's say you have the following .proto file, where the imported status.proto is just a copy-paste of the gRPC status.proto as per this question:

syntax = "proto3";

package test;

import "status.proto";

option go_package = ".;main";

message Foo {
  string a = 1;
  google.rpc.Status status = 2;
}

with directory structure as:

/protos
|_ status.proto
|_ test.proto

and you compile the above with:

cd protos && protoc -I=. --go_out=. test.proto

breathe ...then the generated Go code will have the following import

import (
    status "google.golang.org/genproto/googleapis/rpc/status"
)

and you must satisfy that by go get google.golang.org/genproto.

So about your first question, you can only use Status from googleapis in proto files, because that's how status.proto declares its Go package.

Using generated googleapis Status in Go code

Since the imported Go package is from googleapis that is what you must use in your Go code in order to initialize such messages:

package main

import (
	"fmt"
	googleapis "google.golang.org/genproto/googleapis/rpc/status"
)

func main() {
	foo := &Foo{
		A: "foo",
		Status: &googleapis.Status{
			Code:    int32(code.Code_OK),
			Message: "all good",
		},
	}
	// fmt.Println(foo)
}

Yes but I must use grpc-go Status in my Go code

You can't. protoc generates code with the packages described above. If you absolutely NEED to construct these Status fields using grpc-go, you can use Status.Proto:

package main

import (
	"fmt"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

func main() {
	foo := &Foo{
		A: "foo",
		Status: status.New(codes.OK, "all good").Proto(),
	}
	fmt.Println(foo)
}

Just for the record, the opposite is also possible with status.FromProto:

package main

import (
	"fmt"
	googleapis_codes "google.golang.org/genproto/googleapis/rpc/code"
	googleapis "google.golang.org/genproto/googleapis/rpc/status"
	"google.golang.org/grpc/status"
)

func main() {
	gapisStatus := &googleapis.Status{
		Code: int32(googleapis_codes.Code_OK),
		Message: "all good",
	}
	grpcStatus := status.FromProto(gapisStatus)
	fmt.Println(grpcStatus)
}

<hr>

As a less well-behaved alternative, you can simply copy-paste the status.proto sources into your project and manually change the go_package:

option go_package = &quot;google.golang.org/grpc/status;status&quot;;

This way protoc will generate the Go code with this import, and your own sources will be able to follow suit. Of course this means you now have your own fork of status.proto.

huangapple
  • 本文由 发表于 2021年11月4日 03:22:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/69830652.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定