Error handling in gRPC (Kotlin version)

I've investigated how to handle an error on the gRPC server, so I'll summarize it. Only the case of Unary RPCs (Simple RPC) is described. We do not consider when using Streaming.

The point is to use the message structure that Google officially recommends.

Specifically, use the message structure of error_details.proto recommended by gRPC official website. This proto file is included in proto-google-common-protos- {version} .jar. Various message types are defined in this proto file, and the corresponding Java classes are also included in the same JAR. So you can use these message types in both client-side and server-side application code.

Of course, you can define your own message structure, but you can avoid the "waste of reinventing the wheel" by using something that Google has thought out based on your experience. There is no way not to use it. (Actually, I think it would be more convenient to set up PJ's own error structure by referring to the message structure that Google thought about.)

The total amount of source code introduced this time is below. https://github.com/nyandora/grpc-kotlin-trial

Server side

private class HelloWorldService : GreeterGrpcKt.GreeterCoroutineImplBase() {
  override suspend fun sayHello(request: HelloRequest): HelloReply {
    //Perform validation.
    validate(request)

    return HelloReply.newBuilder().setMessage(request.name).build()
  }

  private fun validate(request: HelloRequest) {
    //The domain of the name property is "a string of less than 10 characters".
    if (request.name.length >= 10) {
      val nameFieldError = BadRequest.FieldViolation.newBuilder()
            .setField("name")
            .setDescription("More than 10 characters are not allowed.").build()

      val badRequestError = BadRequest.newBuilder()
            .addFieldViolations(nameFieldError).build()

      val errorDetail = Metadata()
      errorDetail.put(ProtoUtils.keyForProto(badRequestError), badRequestError)

      throw StatusException(Status.INVALID_ARGUMENT, errorDetail)
    }
  }
}

point

If you throw a io.grpc.StatusException in your application code, the stub code generated by gRPC's Kotlin code generator will handle it and respond appropriately to the client. The code generator assumed here is the one used in the tutorial on the gRPC official website. Specifically, I am using "protoc-gen-grpc-kotlin" in this github repository as a Kotlin code generator.

Set StatusException to google-defined gRPC status code. If you have problems with the input values, as in the example above, use Status.INVALID_ARGUMENT.

The StatusException is also filled with detailed error information. Set only the minimum necessary information that can achieve the following purposes.

If you have problems with the input values, as in the example above, use com.google.rpc.BadRequest. As you can see from error_details.proto, this BadRequest defines FieldViolation as a nested message type. FieldViolation can be filled with error information that occurred for each field. Since you can set multiple FieldViolations in BadRequest, you can also check the total amount of input values ​​and return all the errors that occur to the client at once.

Use io.grpc.Metadata to fill StatusException with detailed error information. Set the Metadata object with detailed error information and pack it in a StatusException.

Client side

class HelloWorldClient(private val channel: ManagedChannel) : Closeable {
  private val stub: GreeterGrpcKt.GreeterCoroutineStub = GreeterGrpcKt.GreeterCoroutineStub(channel)

  suspend fun greet(name: String) {
    val request = HelloRequest.newBuilder().setName(name).build()
    try {
      val response = stub.sayHello(request)
      println("Received: ${response.message}")
    } catch (e: StatusException) {
      if (e.status == Status.INVALID_ARGUMENT) {
        val badReqErrDetail = e.trailers[ProtoUtils.keyForProto(BadRequest.getDefaultInstance())]
        badReqErrDetail?.fieldViolationsList?.forEach {
          println("error occurred at ${it.field}")
          println("error description: ${it.description}")
        }
      }
    }
  }

  //Abbreviation
}

point

If you get an error response as a result of a gRPC call, your client code will throw a StatusException. The client can determine the status code and retrieve detailed error information from StatusException.

Cases other than validation errors

In the above example, com.google.rpc.BadRequest is used when a validation error occurs. However, the errors that occur are not limited to validation errors. Use the appropriate message structure depending on the nature of the error. error_details.proto defines various message structures that can be used in the event of an error. Let's refer to it as appropriate and use it properly.

reference

Google API Design Guide: https://cloud.google.com/apis/design/errors

Recommended Posts

Error handling in gRPC (Kotlin version)
Tips for gRPC error handling in Ruby
Error handling in Graphql-ruby
HMAC in Kotlin
[Swift] Error handling
Big Decimal in Kotlin
Avoid Yubaba's error in Java
NameError in Income # index error
ActiveRecord :: NotNullViolation in Devise error
[Ruby] Exception handling in functions
[Java] Get KClass in Java [Kotlin]
npm error in docker tutorial
[Rails] Rails version upgrade error memorandum
Error in Spring database connection
Try using gRPC in Ruby
Exception handling techniques in Java
Apache POI Excel in Kotlin
Implementation of HashMap in kotlin
Error "MySQL said: Protocol mismatch; server version = 11, client version = 10" in Docker x MySQL8