Created
May 6, 2021 11:43
-
-
Save uzzz/90e7972867b598fac22a7048c82257b7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1. Errors in details | |
service HelloService { | |
rpc SayHello(HelloRequest) returns (HelloResponse); | |
} | |
message HelloRequest { | |
string name = 1; | |
} | |
message HelloResponse { | |
string greeting = 1; | |
} | |
message Error { | |
string message = 1; | |
} | |
// server implementation | |
func (s *HelloService) SayHello( | |
ctx context.Context, | |
r *HelloRequest, | |
) (*HelloResponse, error) { | |
if r.Name == "" { | |
st := status.New(codes.InvalidArgument, "invalid argument") | |
st, err := st.WithDetails(&Error{Message: "name is required"}) | |
if err != nil { | |
return nil, status.Error(codes.Internal, "internal error") | |
} | |
return nil, st.Err() | |
} | |
return &HelloResponse{Greeting: "Hello," + r.Name}, nil | |
} | |
// client implementation | |
resp, err := client.SayHello(ctx, &HelloRequest{Name: "Maksim"}) | |
if err != nil { | |
st, ok := status.FromError(err) | |
if !ok { | |
log.Error("failed to say hello", "error", err) | |
} | |
switch st.Code() { | |
case codes.InvalidArgument: | |
for _, detail := range st.Details() { | |
er, ok := detail.(*Error) | |
if !ok { | |
log.Error("details is not error") | |
break | |
} | |
// display error | |
} | |
} | |
} | |
// ... deal with resp | |
Pros: | |
- Request & response proto definitions are clean and cover happy path | |
- We can write generic code, for example GRPC logging interceptor that logs error with details if code != OK | |
Cons: | |
- Details are untyped: can accidentaly put anything there | |
2. Errors as a part of response | |
service HelloService { | |
rpc SayHello(HelloRequest) returns (HelloResponse); | |
} | |
message HelloRequest { | |
string name = 1; | |
} | |
message HelloResponse { | |
string greeting = 1; | |
repeated Error errors = 2; | |
} | |
message Error { | |
string message = 1; | |
} | |
// server implementation | |
func (s *HelloService) SayHello( | |
ctx context.Context, | |
r *HelloRequest, | |
) (*HelloResponse, error) { | |
if r.Name == "" { | |
return &HelloResponse{Errors: []*Error{Message: "name is required"}}, nil | |
} | |
return &HelloResponse{Greeting: "Hello," + r.Name}, nil | |
} | |
// client implementation | |
resp, err := client.SayHello(ctx, &HelloRequest{Name: "Maksim"}) | |
if err != nil { | |
log.Error("failed to say hello", "error", err) | |
} | |
if len(resp.Errors) > 0 { | |
for _, err := range resp.Errors { | |
// display error | |
} | |
} | |
Pros: | |
- Errors are explicit and typed | |
- Less code | |
Cons: | |
- Message (HelloResponse) has two responsibilities - nothing stops user from setting actual data AND errors at the same time | |
- We don't utilize GRPC status codes, so can't use generic code (loggers, metrics handlers, etc) | |
2.1. Errors as a part of response using oneof | |
service HelloService { | |
rpc SayHello(HelloRequest) returns (HelloResponse); | |
} | |
message HelloRequest { | |
string name = 1; | |
} | |
message HelloResponse { | |
message HelloSuccessResponse { | |
string greeting = 1; | |
} | |
message HelloErrorResponse { | |
repeated Error errors = 1; | |
} | |
oneof response { | |
HelloSuccessResponse success_response = 1; | |
HelloErrorResponse error_response = 2; | |
} | |
} | |
message Error { | |
string message = 1; | |
} | |
This is a variant of of 2, but | |
Pro: | |
- We can't set success & error at the same time | |
Con: | |
- We need extra wrappers |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment