Error handling in GRPC (C++ primarily)

1,489 views
Skip to first unread message

pavol.o...@gmail.com

unread,
Dec 12, 2017, 11:24:15 AM12/12/17
to grpc.io
Hello,

Two of our teams independently defined several RPC calls. Both wanted to cover this usecase:

We have situation when command did not do what the caller wanted. For example (it is just an example, we do not have such code):

service Users {
  rpc
Login(loginRequest) returns (loginResponse) {
 
}
}

message loginRequest
{
 
string username = 1;
 
string password = 2;
 
string super_secret1 = 3;
  string super_secret2 = 4;
}

message loginResponse {
...
}

They want to handle some application-level errors. Each error itself brings more than just Error Code + String. For example, it brings fields that must be filled in to go on. Teams converged to this loginResponse

message loginResponse {
  message
Success {
   
string token = 1;
   
int some_param = 2;
 
}

  message  
Secret1Needed {
   
params of this requirement
 
}

  message  
Secret2Needed {
   
params of this requirement
 
}

  oneof result
{
   
Secret1Needed error1 = 1;
   
Secret2Needed error2 = 2;
   
Success success = 3;
 
}
}

Only token returned is considered as good result (because it actually performed Login). In the opposite case, there might be more than one reason to fail. The context of failure (beyond the verbosity of Status Code + Message) is transferred in specialized messages (here Secret1Needed and Secret2Needed).

In the code, they just handle it as a switch:

switch (loginResponse.result_case()) {
  case ERROR1: %handler ...
  case ERROR2: %handler ...
  case SUCCESS: %handler ...
}

I do not like that. Even worse, to allow for changes, every CommandResponse message now contains oneof(ERROR1, 2, 3..., SUCCESS)

If google wanted to use GRPC like this, they would have defined ERROR and SUCCESS messages instead of commandResponse. However, both teams ended up with this solution. Is there something I am missing? How to handle application-level errors with more context? And how to handle typed contexts (we wanted them to be protobuf messages as well).

Thank you


Arpit Baldeva

unread,
Dec 12, 2017, 2:23:10 PM12/12/17
to grpc.io
We use google.rpc.Status for such use cases (each response contains one) - https://github.com/googleapis/googleapis/blob/master/google/rpc/status.proto.

Evan Jones

unread,
Dec 12, 2017, 5:29:30 PM12/12/17
to grpc.io
I think the "right" option is going to depends on your situation and point of view:


1. This is totally acceptable

To be able to handle errors from Login, I need to understand the application. Therefore, I can't reuse any of the error handling code between this request and other requests, so this seems fine.


2. Use "generic" gRPC codes and status messages

I don't need any specific data back from Login, so I might as well "fail" the gRPC request, and return a gRPC error, since the application will need to handle gRPC errors anyway. As an example, Google Cloud's Spanner API documents their use of the existing gRPC error codes and messages: "Queries inside read-write transactions might return ABORTED. If this occurs, the application should [...]" from https://cloud.google.com/spanner/docs/reference/rpc/google.spanner.v1


3. Use the google.rpc.Status type

This is sort of a compromise: You still need to know what custom-defined type is in the details field, but the gRPC client libraries provide reasonable support for returning and digging out these errors. For example, in Go: https://godoc.org/google.golang.org/grpc/status


Personally, I strive to have my code use option #2, and try not to worry about returning "details." So far we've been able to make this work, but I can see that it won't work in all cases. In that case, I would probably go with #3, since "naive" clients still need to handle those errors, but "sophisticated" clients could do something special.
Reply all
Reply to author
Forward
0 new messages