micronaut-projects / micronaut-grpc

Integration between Micronaut and GRPC
Apache License 2.0
69 stars 39 forks source link

Although I can generate the stubs with io.grpc:protoc-gen-grpc-kotlin I can't implement it #257

Open jimisdrpc opened 3 years ago

jimisdrpc commented 3 years ago

Issue: I can't implement an object autogenerated from protobuf.

How reproduce: generated a project from micronaut initializer with: gRPC Application type, Java 11, Kotlin. Add io.grpc:protoc-gen-grpc-kotlin on build.gradle, build it, add a controller and try implement GrpcdemoServiceGrpcKt.

image

build.gradle

plugins {
    id "org.jetbrains.kotlin.jvm" version "1.4.10"
    id "org.jetbrains.kotlin.kapt" version "1.4.10"
    id "org.jetbrains.kotlin.plugin.allopen" version "1.4.10"
    id "com.github.johnrengelman.shadow" version "6.1.0"
    id "io.micronaut.application" version '1.0.5'
    id "com.google.protobuf" version "0.8.13"
}

version "0.1"
group "com.mybank"

repositories {
    mavenCentral()
    jcenter()
}

micronaut {
    testRuntime "junit5"
    processing {
        incremental true
        annotations "com.mybank.*"
    }
}

dependencies {
    implementation("io.micronaut:micronaut-validation")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlinVersion}")
    implementation("org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}")
    implementation("io.micronaut.kotlin:micronaut-kotlin-runtime")
    implementation("io.micronaut:micronaut-runtime")
    implementation("io.micronaut.grpc:micronaut-grpc-runtime")
    implementation("javax.annotation:javax.annotation-api")
    runtimeOnly("ch.qos.logback:logback-classic")
    runtimeOnly("com.fasterxml.jackson.module:jackson-module-kotlin")
    testImplementation("io.micronaut:micronaut-http-client")

    implementation("io.grpc:grpc-kotlin-stub:${grpcKotlinVersion}")
}

mainClassName = "com.mybank.ApplicationKt"
java {
    sourceCompatibility = JavaVersion.toVersion('11')
}

compileKotlin {
    kotlinOptions {
        jvmTarget = '11'
    }
}
compileTestKotlin {
    kotlinOptions {
        jvmTarget = '11'
    }
}

sourceSets {
    main {
        java {
            srcDirs 'build/generated/source/proto/main/grpc'
            srcDirs 'build/generated/source/proto/main/java'
        }
    }
}

protobuf {
    protoc { artifact = "com.google.protobuf:protoc:3.13.0" }
    plugins {
        grpc { artifact = "io.grpc:protoc-gen-grpc-java:1.32.1" }
        grpckt { artifact = "io.grpc:protoc-gen-grpc-kotlin:${grpcKotlinVersion}" }
    }
    generateProtoTasks {
        all()*.plugins {
            grpc {}
            grpckt {}
        }
    }
}

gradle.properties

micronautVersion=2.1.3
kotlinVersion=1.4.10
grpcKotlinVersion=0.1.2

auto generated stubs while gradle build

package com.mybank

import com.mybank.GrpcdemoServiceGrpc.getServiceDescriptor
import io.grpc.CallOptions
import io.grpc.CallOptions.DEFAULT
import io.grpc.Channel
import io.grpc.Metadata
import io.grpc.MethodDescriptor
import io.grpc.ServerServiceDefinition
import io.grpc.ServerServiceDefinition.builder
import io.grpc.ServiceDescriptor
import io.grpc.Status.UNIMPLEMENTED
import io.grpc.StatusException
import io.grpc.kotlin.AbstractCoroutineServerImpl
import io.grpc.kotlin.AbstractCoroutineStub
import io.grpc.kotlin.ClientCalls.unaryRpc
import io.grpc.kotlin.ServerCalls.unaryServerMethodDefinition
import io.grpc.kotlin.StubFor
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.jvm.JvmOverloads
import kotlin.jvm.JvmStatic

/**
 * Holder for Kotlin coroutine-based client and server APIs for com.mybank.GrpcdemoService.
 */
object GrpcdemoServiceGrpcKt {
  @JvmStatic
  val serviceDescriptor: ServiceDescriptor
    get() = GrpcdemoServiceGrpc.getServiceDescriptor()

  val sendMethod: MethodDescriptor<GrpcdemoRequest, GrpcdemoReply>
    @JvmStatic
    get() = GrpcdemoServiceGrpc.getsendMethod()

  /**
   * A stub for issuing RPCs to a(n) com.mybank.GrpcdemoService service as suspending coroutines.
   */
  @StubFor(GrpcdemoServiceGrpc::class)
  class GrpcdemoServiceCoroutineStub @JvmOverloads constructor(
    channel: Channel,
    callOptions: CallOptions = DEFAULT
  ) : AbstractCoroutineStub<GrpcdemoServiceCoroutineStub>(channel, callOptions) {
    override fun build(channel: Channel, callOptions: CallOptions): GrpcdemoServiceCoroutineStub =
        GrpcdemoServiceCoroutineStub(channel, callOptions)

    /**
     * Executes this RPC and returns the response message, suspending until the RPC completes
     * with [`Status.OK`][io.grpc.Status].  If the RPC completes with another status, a
     * corresponding
     * [StatusException] is thrown.  If this coroutine is cancelled, the RPC is also cancelled
     * with the corresponding exception as a cause.
     *
     * @param request The request message to send to the server.
     *
     * @return The single response from the server.
     */
    suspend fun send(request: GrpcdemoRequest): GrpcdemoReply = unaryRpc(
      channel,
      GrpcdemoServiceGrpc.getSendMethod(),
      request,
      callOptions,
      Metadata()
    )}

  /**
   * Skeletal implementation of the com.mybank.GrpcdemoService service based on Kotlin coroutines.
   */
  abstract class GrpcdemoServiceCoroutineImplBase(
    coroutineContext: CoroutineContext = EmptyCoroutineContext
  ) : AbstractCoroutineServerImpl(coroutineContext) {
    /**
     * Returns the response to an RPC for com.mybank.GrpcdemoService.send.
     *
     * If this method fails with a [StatusException], the RPC will fail with the corresponding
     * [io.grpc.Status].  If this method fails with a [java.util.concurrent.CancellationException],
     * the RPC will fail
     * with status `Status.CANCELLED`.  If this method fails for any other reason, the RPC will
     * fail with `Status.UNKNOWN` with the exception as a cause.
     *
     * @param request The request from the client.
     */
    open suspend fun send(request: GrpcdemoRequest): GrpcdemoReply = throw
        StatusException(UNIMPLEMENTED.withDescription("Method com.mybank.GrpcdemoService.send is unimplemented"))

    final override fun bindService(): ServerServiceDefinition = builder(getServiceDescriptor())
      .addMethod(unaryServerMethodDefinition(
      context = this.context,
      descriptor = GrpcdemoServiceGrpc.getSendMethod(),
      implementation = ::send
    )).build()
  }
}

All the rest are exactly the same from micronaut.launch

Possible solution: there is an example I downloaded and staret it successsfuly and called it from BloomRPC. It is from oficial examples. Looking at it I see a much more complex gradle.

plugins {
    id "org.jetbrains.kotlin.jvm" version "1.3.72"
    id "org.jetbrains.kotlin.kapt" version "1.3.72"
    id "org.jetbrains.kotlin.plugin.allopen" version "1.3.72"
    id "application"
    id 'com.google.protobuf' version '0.8.13'
}

version "0.2"
group "helloworld"

repositories {
    mavenLocal()
    jcenter()
}

configurations {
    // for dependencies that are needed for development only
    developmentOnly
}

dependencies {
    kapt(enforcedPlatform("io.micronaut:micronaut-bom:$micronautVersion"))
    kapt("io.micronaut:micronaut-inject-java")
    kapt("io.micronaut:micronaut-validation")

    implementation(enforcedPlatform("io.micronaut:micronaut-bom:$micronautVersion"))
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlinVersion}")
    implementation("org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxCoroutinesVersion")
    implementation("io.micronaut:micronaut-runtime")
//    implementation("io.micronaut.grpc:micronaut-grpc-runtime")
    implementation("io.micronaut.grpc:micronaut-grpc-server-runtime:$micronautGrpcVersion")
    implementation("io.micronaut.grpc:micronaut-grpc-client-runtime:$micronautGrpcVersion")
    implementation("io.grpc:grpc-kotlin-stub:${grpcKotlinVersion}")

    runtimeOnly("ch.qos.logback:logback-classic:1.2.3")
    runtimeOnly("com.fasterxml.jackson.module:jackson-module-kotlin:2.9.8")

    kaptTest("io.micronaut:micronaut-inject-java")

    testImplementation enforcedPlatform("io.micronaut:micronaut-bom:$micronautVersion")
    testImplementation("org.junit.jupiter:junit-jupiter-api:5.3.0")
    testImplementation("io.micronaut.test:micronaut-test-junit5")
    testImplementation("org.mockito:mockito-junit-jupiter:2.22.0")

    testRuntime("org.junit.jupiter:junit-jupiter-engine:5.3.0")
    testRuntime("org.jetbrains.spek:spek-junit-platform-engine:1.1.5")
}

test.classpath += configurations.developmentOnly

mainClassName = "helloworld.Application"

test {
    useJUnitPlatform()
}

allOpen {
    annotation("io.micronaut.aop.Around")
}

compileKotlin {
    kotlinOptions {
        jvmTarget = '1.8' 
        //Will retain parameter names for Java reflection
        javaParameters = true 
    }
}
//compileKotlin.dependsOn(generateProto)

compileTestKotlin {
    kotlinOptions {
        jvmTarget = '1.8' 
        javaParameters = true 
    }
}

tasks.withType(JavaExec) {
    classpath += configurations.developmentOnly
    jvmArgs('-XX:TieredStopAtLevel=1', '-Dcom.sun.management.jmxremote')
}

sourceSets {
    main {
        java {
            srcDirs 'build/generated/source/proto/main/grpc'
            srcDirs 'build/generated/source/proto/main/grpckt'
            srcDirs 'build/generated/source/proto/main/java'
        }
    }
}

protobuf {
    protoc { artifact = "com.google.protobuf:protoc:${protocVersion}" }
    plugins {
        grpc { artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" }
        grpckt { artifact = "io.grpc:protoc-gen-grpc-kotlin:${grpcKotlinVersion}" }
    }
    generateProtoTasks {
        all()*.plugins {
            grpc {}
            grpckt {}
        }
    }
}

Question: what I am missing in order to implement the autogenerated stubs? Do I need more gradle dependencies beyond io.grpc:protoc-gen-grpc-kotlin? Am I in right direction? If not, what should I do in order to implement the send rpc method from demo project downloaded from Micronaut.launch?

PS.: when I tried the most recent version from io.grpc:protoc-gen-grpc-kotlin gradle complains so I just use 0.1.2 which is the same from official example. This is not an issue for me as long as it is not related to my problem.

jimisdrpc commented 3 years ago

Sorry for terrible format. I added the code inside the "Insert Code" space created. I tried re-edit but I couldn't. Basically I added two main files: build.gradle that I downloaded from micronaut.launch and I have just added io.grpc:protoc-gen-grpc-kotlin and the build.gradle from official example. Hopefully who reads it can understand it.

jeffscottbrown commented 3 years ago

For reference: https://stackoverflow.com/questions/64803461/although-i-can-generate-the-stubs-with-io-grpcprotoc-gen-grpc-kotlin-i-cant-im

jimisdrpc commented 3 years ago

@jeffbrown in case it helps, I tried copied all build.gradle from example provided to the project generated from Micronaut.Launch and I had the same issue. On another hand, the inverse works: I copied my proto file to the example provided and it works. This gives me a clue that there is something extra in the example provided that is mmissed in project generated from micronaut.launch

jeffscottbrown commented 3 years ago

I tried copied all build.gradle from example provided to the project generated from Micronaut.Launch and I had the same issue

If the example you are talking about is the one at github.com/micronaut-projects/micronaut-grpc/tree/c8ec599ad1b696d432efecb10145105074f3d26a/examples/hello-world-kotlin, be aware that project is using Micronaut 2.0.0.M3 and your project is using Micronaut 2.1.3.

jimisdrpc commented 3 years ago

@jeffbrown thanks. Well, my project is generated from Micronaut Laucher. I assume this is the best source for starting a fresh and up-to-date project with proper dependencie versions. Have you successfuly downloaded a grpc application from micronaut launcher and created successfully a proto and its endpoint in Kotlin? If so than would mind to share with me?

morrle commented 3 years ago

modify sourceSets in build.gradle file

sourceSets {
    main {
        java {
            srcDirs 'build/generated/source/proto/main/grpc'
            srcDirs 'build/generated/source/proto/main/grpckt' // load the generated kotlin file
            srcDirs 'build/generated/source/proto/main/java'
        }
    }
}
jimisdrpc commented 3 years ago

@morrle thanks, but same issue. May I ask you if you can share with me a very simple project evoulated from Micronaut Launcher with Kotlin? I am really stuck.

I tried again. The steps are:

1 - I generated grpcApplication/kotlin from Micronaut Launcher. 2 - added these lines in build.gradle

plugins {
 ...
    id "com.google.protobuf" version "0.8.13"
...
dependencies {
...
    implementation("io.grpc:grpc-kotlin-stub:0.1.2")
...
sourceSets {
    main {
        java {
...
            srcDirs 'build/generated/source/proto/main/grpckt' // load the generated kotlin file
...
protobuf {
...
            grpckt {}}

3 - I tried to implement the proto example which came with autogenerated project

import javax.inject.Singleton

@Singleton
class MyEndpoint : GrpcdemoServiceGrpcKt.GrpcdemoServiceCoroutineImplBase() {
    override suspend fun send(request: GrpcdemoRequest): GrpcdemoReply {
        return GrpcdemoReply.newBuilder().setMessage("test").build()
    }
}

Here is the error when I tried gradle clean build

image

Here is the whole build.gradle

plugins {
    id "org.jetbrains.kotlin.jvm" version "1.4.10"
    id "org.jetbrains.kotlin.kapt" version "1.4.10"
    id "org.jetbrains.kotlin.plugin.allopen" version "1.4.10"
    id "com.github.johnrengelman.shadow" version "6.1.0"
    id "io.micronaut.application" version '1.0.5'

    id "com.google.protobuf" version "0.8.13"
}

version "0.1"
group "com.mybank"

repositories {
    mavenCentral()
    jcenter()
}

micronaut {
    testRuntime "junit5"
    processing {
        incremental true
        annotations "com.mybank.*"
    }
}

dependencies {
    implementation("io.micronaut:micronaut-validation")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlinVersion}")
    implementation("org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}")
    implementation("io.micronaut.kotlin:micronaut-kotlin-runtime")
    implementation("io.micronaut:micronaut-runtime")
    implementation("io.micronaut.grpc:micronaut-grpc-runtime")
    implementation("javax.annotation:javax.annotation-api")

    implementation("io.grpc:grpc-kotlin-stub:0.1.2")

    runtimeOnly("ch.qos.logback:logback-classic")
    runtimeOnly("com.fasterxml.jackson.module:jackson-module-kotlin")
    testImplementation("io.micronaut:micronaut-http-client")

}

mainClassName = "com.mybank.ApplicationKt"
java {
    sourceCompatibility = JavaVersion.toVersion('11')
}

compileKotlin {
    kotlinOptions {
        jvmTarget = '11'
    }
}
compileTestKotlin {
    kotlinOptions {
        jvmTarget = '11'
    }
}

sourceSets {
    main {
        java {
            srcDirs 'build/generated/source/proto/main/grpc'

            srcDirs 'build/generated/source/proto/main/grpckt' // load the generated kotlin file
            srcDirs 'build/generated/source/proto/main/java'
        }
    }
}

protobuf {
    protoc { artifact = "com.google.protobuf:protoc:3.13.0" }
    plugins {
        grpc { artifact = "io.grpc:protoc-gen-grpc-java:1.32.1" }

        grpckt { artifact = "io.grpc:protoc-gen-grpc-kotlin:0.1.2" }
    }
    generateProtoTasks {
        all()*.plugins {
            grpc {}

            grpckt {}}
    }
}

The rest is exactly what I downloaded from micronaut launcher.

jimisdrpc commented 3 years ago

Well, I am still looking forward to find a solution for this. Normally, we expected from frameworks launchers/initializers to be precise with dependencies.

graemerocher commented 3 years ago

@jimisdrpc seems to be an issue specific to Kotlin, will investigate ... thanks for the report

graemerocher commented 3 years ago

So the statement "Normally, we expected from frameworks launchers/initializers to be precise with dependencies." is inaccurate in that frameworks and launchers typically only include libraries and features when a stable release of a library is present and you are not using the raw output from the launcher, you are modifying the code to introduce protoc-gen-grpc-kotlin which is in an alpha/beta/prerelease state

jimisdrpc commented 3 years ago

@graemerocher ok, you are right. I didn't know protoc-gen-grpc-kotlin wasn't in stable version. As far as I can see, looking at https://mvnrepository.com/artifact/io.grpc/protoc-gen-grpc-kotlin I don't get this point. BTW, I can give a try with Java e remove this dependencie.

asarkar commented 3 years ago

@jimisdrpc Have you considered grpc-kotlin by Google? I used it with a Spring Boot project, and although not GA yet, it is doing a good job so far.

jimisdrpc commented 3 years ago

@asarkar , thanks for suggestion. Good to know. I will give a try. It is worth to me as long as it runs well with Micronaut.

marcoscouto commented 3 years ago

@jimisdrpc i think this last error is because in your file .proto you write the "sendMethod" with the first letter in lower case, you should try write like this "SendMethod", i have the same error and i fix it with this change.

asarkar commented 3 years ago

@marcoscouto Naming in proto file is a convention, not a rule. If not following the convention results in an error, it’s a bug.

jimisdrpc commented 3 years ago

@graemerocher Since December 8th I can say https://mvnrepository.com/artifact/io.grpc/protoc-gen-grpc-kotlin/1.0.0 is in stable version, right?

Do you see any point in this build.gradle?

plugins {
    id("org.jetbrains.kotlin.jvm") version "1.4.10"
    id("org.jetbrains.kotlin.kapt") version "1.4.10"
    id("org.jetbrains.kotlin.plugin.allopen") version "1.4.10"
    id("com.github.johnrengelman.shadow") version "6.1.0"
    id("io.micronaut.application") version "1.2.0"
    id("com.google.protobuf") version "0.8.13"
}

version = "0.1"
group = "com.tolearn"

repositories {
    mavenCentral()
    jcenter()
}

micronaut {
    testRuntime("junit5")
    processing {
        incremental(true)
        annotations("com.tolearn.*")
    }
}

dependencies {
    implementation("io.micronaut:micronaut-validation")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlinVersion}")
    implementation("org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}")
    implementation("io.micronaut.kotlin:micronaut-kotlin-runtime")
    implementation("io.micronaut:micronaut-runtime")
    implementation("io.micronaut.grpc:micronaut-grpc-runtime")
    implementation("javax.annotation:javax.annotation-api")
    implementation("io.micronaut:micronaut-http-client")
    implementation("io.micronaut:micronaut-tracing")

    implementation("io.grpc:protoc-gen-grpc-kotlin:1.0.0")

    runtimeOnly("io.jaegertracing:jaeger-thrift")
    runtimeOnly("ch.qos.logback:logback-classic")
    runtimeOnly("com.fasterxml.jackson.module:jackson-module-kotlin")
}

application {
    mainClass.set("com.tolearn.ApplicationKt")
}

java {
    sourceCompatibility = JavaVersion.toVersion("11")
}

tasks {
    compileKotlin {
        kotlinOptions {
            jvmTarget = "11"
        }
    }
    compileTestKotlin {
        kotlinOptions {
            jvmTarget = "11"
        }
    }

}

sourceSets {
    main {
        java {
            srcDirs("build/generated/source/proto/main/grpc")
            srcDirs 'build/generated/source/proto/main/grpckt'
            srcDirs("build/generated/source/proto/main/java")
        }
    }
}

protobuf {
    protoc { artifact = "com.google.protobuf:protoc:3.14.0" }
    plugins {
        grpc { artifact = "io.grpc:protoc-gen-grpc-java:1.33.1" }
        grpckt { artifact = "io.grpc:protoc-gen-grpc-kotlin:1.0.0" }
    }
    generateProtoTasks {
        all()*.plugins {
            grpc {}
            grpckt {}
        }
    }
}

My goal is use protoc-gen-grpc-kotlin so the stubs will be generated in Kotlin with supend function (Coroutine context).

graemerocher commented 3 years ago

Here is an example build.gradle.kts that works for me with Kotlin 1.4.30:

import com.google.protobuf.gradle.*
plugins {
    id("org.jetbrains.kotlin.jvm") version "1.4.30"
    id("org.jetbrains.kotlin.kapt") version "1.4.30"
    id("org.jetbrains.kotlin.plugin.allopen") version "1.4.10"
    id("com.github.johnrengelman.shadow") version "6.1.0"
    id("io.micronaut.application") version "1.3.3"
    id("com.google.protobuf") version "0.8.13"
}

version = "0.1"
group = "tmp"

val kotlinVersion=project.properties.get("kotlinVersion")
repositories {
    mavenCentral()
    jcenter()
}

micronaut {
    testRuntime("junit5")
    processing {
        incremental(true)
        annotations("tmp.*")
    }
}

dependencies {
    implementation("io.micronaut:micronaut-validation")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlinVersion}")
    implementation("org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}")
    implementation("io.micronaut.kotlin:micronaut-kotlin-runtime")
    implementation("io.micronaut:micronaut-runtime")
    implementation("io.micronaut.grpc:micronaut-grpc-runtime")
    implementation("io.grpc:grpc-kotlin-stub:1.0.0")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2")
    runtimeOnly("ch.qos.logback:logback-classic")
    runtimeOnly("com.fasterxml.jackson.module:jackson-module-kotlin")
    testImplementation("io.micronaut:micronaut-http-client")
}

application {
    mainClass.set("tmp.ApplicationKt")
}

java {
    sourceCompatibility = JavaVersion.toVersion("1.8")
}

tasks {
    compileKotlin {
        kotlinOptions {
            jvmTarget = "1.8"
        }
    }
    compileTestKotlin {
        kotlinOptions {
            jvmTarget = "1.8"
        }
    }

}
sourceSets {
    main {
        java {
            srcDirs("build/generated/source/proto/main/grpc")
            srcDirs("build/generated/source/proto/main/java")
        }
    }
}

protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:3.14.0"
    }
    plugins {
        id("grpc") {
            artifact = "io.grpc:protoc-gen-grpc-java:1.33.1"
        }
        id("grpckt") {
            artifact = "io.grpc:protoc-gen-grpc-kotlin:1.0.0:jdk7@jar"
        }        
    }
    generateProtoTasks {
        ofSourceSet("main").forEach {
            it.plugins {
                // Apply the "grpc" plugin whose spec is defined above, without options.
                id("grpc")
                id("grpckt")
            }
        }
    }
}