英文:
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实现
这两个定义的Status
和Codes
的Go包如下:
Google API
- Status:google.golang.org/genproto/googleapis/rpc/status
- Codes:google.golang.org/genproto/googleapis/rpc/code
gRPC
由于我是自己的gRPC服务的客户端,而不是现有Google gRPC API的客户端,我想使用Status
和Code
的gRPC定义。
然而,Status
的gRPC proto文件实际上是从Google API定义中复制的。请参见https://github.com/grpc/grpc/tree/master/src/proto/grpc/status。status.proto的go_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定义的
Status
和Codes
,是正确的吗? -
如果我的断言是正确的,那么当它被打包为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
- Status: google.golang.org/genproto/googleapis/rpc/status
- Codes: google.golang.org/genproto/googleapis/rpc/code
gRPC
- Status: google.golang.org/grpc/status
- Codes: google.golang.org/grpc/codes
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
andCodes
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
文件)没有直接定义使用 Status
或 Code
的消息,那么 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 文件中使用 googleapis
的 Status
,因为这是 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-go
的 Status
你不能。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 = "google.golang.org/grpc/status;status";
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
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论