Surfacing errors to UIs

582 views
Skip to first unread message

Francis Chuang

unread,
Sep 12, 2017, 9:29:26 PM9/12/17
to grpc.io
I am working on an app built as a set of microservices. Each microservice will be built using GRPC using Go. In front of these services, there will be an API gateway (built using GraphQL).

As an example, there is an authentication microservice that authenticates users using their email address and password. There is a SignIn GRPC service method that takes the email address, password and session id of the user. If authentication is successful, we attach the user's id to their session (using the session id).

The problem is that if the authentication fails, we want to surface this to the UI. In the GRPC method implementation, I can return GRPC status codes, for example: return nil, status.Error(codes.InvalidArgument, "could not find user by email address"). In order to work out specifically what is wrong, the client calling this method would need to parse the error message (which is not a great idea).

I've thought about attaching extra details to the error. For example, using bad request: https://godoc.org/google.golang.org/genproto/googleapis/rpc/errdetails#BadRequest

However, the method is littered with multiple copies of this in of each RPC method:

s, err := status.Newf(codes.InvalidArgument, "could not find user by email address").WithDetails(&errdetails.BadRequest{
FieldViolations: []*errdetails.BadRequest_FieldViolation{
{
Description: "EMAIL_ADDRESS_NOT_FOUND",
Field: []string{"SignInRequest.email"},
},
},
})

if err != nil {
return nil, status.Errorf(codes.Internal, "error marshaling status error: %s", err)
}

return nil, s.Err()

In addition, I also need to keep a list of error codes (EMAIL_ADDRESS_NOT_FOUND) in the above example and make sure the clients calling this method checks against them. The API gateway would then look at the error code and surface the appropriate localized error messages to the UI.

The approaches I have tried do not appear to be very nice and have made working on these RPC methods to be a chore and quite unpleasant.

Has anyone else successfully solved this problem? If so, what were your approaches?

Cheers,
Francis

Evan Jones

unread,
Sep 13, 2017, 6:55:09 PM9/13/17
to grpc.io
In general error handling is in my opinion one of the hardest parts of software engineering. :) Silly comments aside, in my limited experience, gRPC seems to encourage "just" returning a gRPC status code and a string. The examples I like to look at are the Google Cloud APIs. For example, the Spanner ExecuteSql says "if [...] the query fails with a FAILED_PRECONDITION error. Queries [...] might return ABORTED. If this occurs, the application should [...]"

So if you can map your application errors to gRPC errors and get what you want, that seems like the "recommended" way.

The other thing I have done in some cases is to redefine "success". For example, maybe the "Login" API should return an object with either the logged in credentials, or a detailed error if they can't be logged in for some reason?


Spanner ExecuteSql:

Francis Chuang

unread,
Sep 13, 2017, 8:16:02 PM9/13/17
to grpc.io
These are all very good points. I've looked at etcd3 as well as a few other projects using GRPC and they just rely on the GRPC status codes.

I guess the root of the problem is probably the granularity of the GRPC status codes. For example, in the service implementation, I might have multiple errors returned as INVALID_ARGUMENT. However, it is not possible to signal which field is invalid in a given request.

I think the only way to do this is to use WithDetails() and the BadRequest protobuf message type.

I have decided on the following strategy:
- Do not use WithDetails() if possible.
- If the client really needs the extra granularity (to implement different behaviors), then use WithDetails().

I think the code is easier to manage this way, however, there's a fair bit of mental overhead/coupling to decide whether the client will require this granularity.

In addition, there is still 1 problem I have not successfully solved: For extra granularity, I am returning strings like "EMAIL_ADDRESS_NOT_FOUND" as the BadRequest's Description as part of WithDetails(). However, this requires a fair bit of bookkeeping in the client. For example, there is no way to signal that the string has changed or has been deprecated without a lot of manual documentation. In addition, there is no nice way to guard against typos when using these strings in the clients.


Arpit Baldeva

unread,
Sep 14, 2017, 12:52:28 PM9/14/17
to grpc.io
I work in C++ but I think the strategy we have can be adopted in any language.

We use Grpc status code for the errors for very limited system level failures. This way, the mapping from our application logic to grpc status codes is limited. An incentive for doing this is streaming rpcs where we may not want to close down the stream because one of the request had some kind of an error. If a grpc status other than OK is returned, the behavior is to close the stream. 

Then, we add google.rpc.status (https://github.com/googleapis/googleapis/blob/master/google/rpc/status.proto ) to each response message. This way, client can check for an error code in a context specific manner for the rpc. This should get rid of the string typo problem you have. You can also provide an error message and even more details using Any message.

On client, it becomes a two level check. While not as simple as I'd like it to be, this is the best design we could think of:

        if (status.ok()) // This is grpc status
        {
            if (reply.status().code() == 0) // This is the google::rpc::status proto embedded in the response message
            {
                outputFile << "Rpc call succeeded. Response is" << std::endl;
                
            }
            else
            {
                outputFile << "Rpc call failed. Error details are" << std::endl;
                
            }
        }
        else
        {
            outputFile << "Rpc call failed." << std::endl;
            outputFile << "System status code (" << gRPCStatusCodeToString(status.error_code()) << "|" << status.error_code() << ")\n" << "System status message (" << status.error_message() << ")" << std::endl;

Evan Jones

unread,
Sep 15, 2017, 10:09:52 AM9/15/17
to grpc.io
Interesting! Thanks for sharing. Just so I'm clear, this means you only use the gRPC status to mean "did the RPC get responded to correctly or not", and the "application errors" are embedded in the response struct. This seems to be a reasonable approach. Do you find that the duplicate error checking (both the gRPC code, and the application code) is not annoying? Do you literally do this for all requests, or just ones that may have more complex error handling logic?

(For clarity, this is basically what I meant by "redefining success": The *RPC* is successful, even if the application request was not).

Thanks!

Evan  

Arpit Baldeva

unread,
Sep 16, 2017, 12:00:31 PM9/16/17
to Evan Jones, grpc.io
Your interpretation is correct.As for the duplicate error checking, yeah, it is not the most convenient thing but given all the other constraints, we settled on it. 

>>Do you literally do this for all requests, or just ones that may have more complex error handling logic
We already have an existing rpc system that allowed for defining a per rpc set of errors and error object and most rpcs already did use that functionality. Grpc came as an additional capability. So yes, we do literally do it for all the rpcs. However, if a system was designed from ground up/grpc only, I can see not wanting to do this for all rpcs. Moreover, all the fields in Proto3 are optional so the client has to always take that into account anyway (meaning that google.rpc.status may or may not be present).

Thanks. 


--
You received this message because you are subscribed to a topic in the Google Groups "grpc.io" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/grpc-io/k6QFNxWDmv0/unsubscribe.
To unsubscribe from this group and all its topics, send an email to grpc-io+unsubscribe@googlegroups.com.
To post to this group, send email to grp...@googlegroups.com.
Visit this group at https://groups.google.com/group/grpc-io.
To view this discussion on the web visit https://groups.google.com/d/msgid/grpc-io/f55a1b62-297f-4c55-9fdb-b649cda400f0%40googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Sarath Sadasivan Pillai

unread,
Jul 24, 2024, 12:27:20 PM7/24/24
to grpc.io
I was wondering how is using the google.rpc.status in response body different from google.rpc.status as error, except the size limit of http header

To unsubscribe from this group and all its topics, send an email to grpc-io+u...@googlegroups.com.

To post to this group, send email to grp...@googlegroups.com.
Visit this group at https://groups.google.com/group/grpc-io.

Purnesh Dixit

unread,
Aug 3, 2024, 5:48:41 PM8/3/24
to grpc.io

I believe the idiomatic way is to use the RPC status. You can attach additional error details, if needed, in the response's trailing metadata.

Purnesh Dixit

unread,
Aug 7, 2024, 11:43:08 AM8/7/24
to grpc.io
I believe the idiomatic way is to use the RPC status. You can attach additional error details, if needed, in the response's trailing metadata.

On Wednesday, July 24, 2024 at 9:57:20 PM UTC+5:30 Sarath Sadasivan Pillai wrote:
Reply all
Reply to author
Forward
0 new messages