A Kotlin kapt annotation processor for using gRPC with the transport of your choice (json, proto, etc.).
Supports client & server creation with coroutines.
Note This project is a preview. Please try it out and let us know what you think, but there are currently no guarantees of any form of stability or support.
To create a gRPC client simply create an interface annotated with @GrpcClient
.
To implement a server, create another class that inherits from your @GrpcClient
,
annotate it with @GrpcServer
, and implement the server-side logic for the methods
that you have defined on the client.
fun main() = runBlocking {
// create the server (use .asGrpcService() to create long running servers)
SimpleServer().asGrpcServer(8080) {
// create a client and query the server
SimpleServiceClient.forAddress("localhost", 8080, channelOptions = { usePlaintext() }).use { client ->
val answer = client.ask(Question("what's this?"))
println(answer)
}
}
}
// define the data types
data class Question(val query: String)
data class Answer(val result: String)
// generate a gRPC client
@GrpcClient
interface SimpleService {
suspend fun ask(question: Question): Answer
}
// generate a gRPC service
@GrpcServer
class SimpleServer : SimpleService {
override suspend fun ask(question: Question) = Answer(result = "you said: '${question.query}'")
}
In most cases, you should also add at least one object in your application with the @GrpcMarshaller
annotation to specify how the data will be marshaled.
Here's another example using JSON and a bidirectional streaming API:
fun main() = runBlocking {
// create the server
ComplexServer().asGrpcServer(8080) {
// create a client and call the server
ComplexServiceClient.forAddress("localhost", 8080, channelOptions = { usePlaintext() }).use { client ->
// bidirectional streaming
val answers = client.debate(produce {
repeat(10) { i -> send(Question("Question #${i + 1}")) }
})
for (answer in answers) {
println(answer)
}
}
}
}
// generate a gRPC client
@GrpcClient
interface ComplexService {
suspend fun debate(questions: ReceiveChannel<Question>): ReceiveChannel<Answer>
}
// generate & implement a gRPC service
@GrpcServer
class ComplexServer : ComplexService {
override suspend fun debate(questions: ReceiveChannel<Question>) = CoroutineScope(coroutineContext).produce {
for (question in questions) {
send(Answer("${question.query}? Sorry, I don't know..."))
}
}
}
// Use JSON serialization for the client & server
@GrpcMarshaller
object MyMarshallerProvider {
private val mapper: ObjectMapper = ObjectMapper().registerModule(KotlinModule())
fun <T> of(type: Class<T>): MethodDescriptor.Marshaller<T> {
return object : MethodDescriptor.Marshaller<T> {
override fun stream(value: T): InputStream = ByteArrayInputStream(mapper.writeValueAsBytes(value))
override fun parse(stream: InputStream): T = stream.bufferedReader().use { mapper.readValue(it, type) }
}
}
}
See the complete example here, a proto example here, and a Google Cloud API example here.
For the adventurous, see the gRPC reflection example here.
You can run the examples by running the following in the root of the project:
$ ./gradlew :runExample
$ ./gradlew :runExampleWithJson
$ ./gradlew :runExampleWithProto
$ ./gradlew :runExampleWithGoogle
$ ./gradlew :runExampleWithProtoReflection
Note: :runExampleWithGoogle
requires a GCP project with the
Langauge API enabled. Set the GOOGLE_APPLICATION_CREDENTIALS
environment variable before running, i.e.:
GOOGLE_APPLICATION_CREDENTIALS=<path_to_service_account.json> ./gradlew :runExampleWithGoogle
It's sort of a simpler version of kgen.
Contributions to this library are always welcome and highly encouraged.
See the CONTRIBUTING documentation for more information on how to get started.
This library is currently a preview with no guarantees of stability or support. Please get involved and let us know if you find it useful and we'll work towards a stable version.
This is not an official Google product.