Protokruft is a simple Gradle plugin for generating a Kotlin DSL from generated Java Protobuf sources. It removes the worst of the Java boilerplate that is generated, leaving you with a nice clean syntax in your Kotlin. The target classes are:
- messages which extend
GeneratedMessageV3
. - RPC Server interfaces which extend
BindableService
orXXXImplBase
- RPC Client interfaces which extend
BlockingStub
By default, the generated classes are put next to the Java files, into the build/generated/source/proto/main/java
folder.
Given this proto:
syntax = "proto3";
option java_package = "org.protokruft.example1";
package AnExample1;
service CarService {
rpc GetEngine (Car) returns (Engine) {}
}
message Car {
string model = 1;
Engine engine = 2;
}
message Engine {
int32 cc = 1;
int32 bhp = 2;
}
The generated Java code from the Google protoc would be used like this:
val person = Person.newBuilder()
.setName("Hello Kitty")
.setAddress(
Address.newBuilder()
.setNumber(123)
.setStreet("Hello Kitty Street")
.setPostcode("N304SD")
.build())
.build()
Sprinkle on some Protokruft, and you can use it like this to hide the newBuilder().build()
boilerplate:
val person = newPerson {
name = "Hello Kitty"
address = newAddress {
number = 123
street = "Hello Kitty Street"
postcode = "N304SD"
}
}
... or like this to scope the Builder
as it
:
val personScoped = newPerson.also {
it.name = "Hello Kitty"
it.address = newAddress.also {
it.number = 123
it.street = "Hello Kitty Street"
it.postcode = "N304SD"
}
}
For services, Protokruft generates this interface which wraps the final class generated by GRPC, allowing you to mock the service interfaces:
interface CarService {
fun getEngine(car: Example1.Car): Example1.Engine
object Grpc {
object Client {
operator fun invoke(channel: Channel): CarService {
val stub = CarServiceGrpc.newBlockingStub(channel)
return object : CarService {
override fun getEngine(car: Example1.Car): Example1.Engine = stub.getEngine(car)
}
}
}
object Server {
operator fun invoke(delegate: PersonService): BindableService = object : PersonServiceImplBase() {
override fun getAddress(person: Example2.Person, responseObserver: StreamObserver<Example2.Address>) {
responseObserver.onNext(delegate.getAddress(person))
responseObserver.onCompleted()
}
}
}
}
}
val newBlockingStub: CarService = CarService.Grpc.Client(channel)
val grpcService: BindableService = CarService.Grpc.Server(myCarServiceImplementation)
Optionally, you can also add a markerInterface
option to aid IDE navigability.
Merge the following sections into your Gradle file. This assumes that you've already got Protobuf classes being generated by the protobuf-gradle-plugin
:
buildscript {
dependencies {
classpath 'org.protokruft:protokruft:2.X.X'
}
}
repositories {
jcenter()
}
apply plugin: 'protokruft'
protokruft {
packageNames = ["com.mygreatpackage"] // "*" by default
messageClassFile = "customMessage" // "messageDsl" by default
messageDslPrefix = "customPrefix" // "new" by default
serviceClassFile = "customService" // "serviceDsl" by default
serviceDslSuffix = "CustomSuffix" // "" by default
markerInterface = "com.my.great.Interface" // null by default
}
// allows you to just run generateProtoDsl
generateProtobufDsl.dependsOn('generateProto')
Then just run: ./gradlew generateProtobufDsl
NB: To generate classes correctly, your project should be using GRPC v1.18.0 or higher of the GRPC libraries.