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).
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