面向Kotlin的gRPC/protobuf库:Kroto+使用介绍

面向Kotlin的gRPC/protobuf库:Kroto+使用介绍_第1张图片

Kroto+(Kroto-plus)


在这里插入图片描述
Kroto+是一个面向Kotlin的gRPC库
https://github.com/marcoferrer/kroto-plus

以往,想在Kotlin上使用protobuf协议进行grpc通信,需要基于java生成本地stub代码然后在kotlin中调用,现在通过Kroto+可以直接生成更加kotlin范儿的本地stub,包括对Coroutine的支持、支持DSL等,非常方便!

// before
val message1 = MessageRequest.newBuilder()
    .setMessage("hello")
    .build()

// Kroto+
val message2 = MessageRequest { 
    message = "hello"
}

接下来通过一个例子了解一下=Kproto+的使用


1.build.gradle


gradle.properties定义版本号

krotoplus_version=0.5.0
protobuf_version=3.10.0
coroutines_version=1.3.2
grpc_version=1.25.0

添加protobuf的plugin

plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.3.61'
    id 'com.google.protobuf' version '0.8.10' // 添加protobuf
    id 'application'
}

添加dependencies依赖

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"

    implementation "com.github.marcoferrer.krotoplus:kroto-plus-coroutines:$krotoplus_version"
    implementation "com.github.marcoferrer.krotoplus:kroto-plus-message:$krotoplus_version"

    implementation "com.google.protobuf:protobuf-java:$protobuf_version"

    implementation "io.grpc:grpc-protobuf:$grpc_version"
    implementation "io.grpc:grpc-stub:$grpc_version"
    implementation "io.grpc:grpc-netty:$grpc_version"
}

配置compileKotlin

compileKotlin {
    kotlinOptions {
        jvmTarget = "1.8"
        freeCompilerArgs += [
                "-Xuse-experimental=kotlinx.coroutines.ObsoleteCoroutinesApi"
        ]
    }
}
compileTestKotlin {
    kotlinOptions.jvmTarget = "1.8"
}

配置protobuf的task

protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:$protobuf_version"
    }

    plugins {
        grpc {
            artifact = "io.grpc:protoc-gen-grpc-java:$grpc_version"
        }
        coroutines {
            artifact = "com.github.marcoferrer.krotoplus:protoc-gen-grpc-coroutines:$krotoplus_version:jvm8@jar"
        }
        kroto {
            artifact = "com.github.marcoferrer.krotoplus:protoc-gen-kroto-plus:$krotoplus_version:jvm8@jar"
        }
    }

    generateProtoTasks {
        def krotoConfig = file("krotoPlusConfig.asciipb")

        all().each { task ->

            task.inputs.files krotoConfig

            task.plugins {
                grpc {}
                coroutines {}
                kroto {
                    outputSubDir = "java"
                    option "ConfigPath=$krotoConfig"
                }
            }
        }
    }
}

generateProtoTasks.krotoConfig中设置的krotoPlusConfig.asciipb是生成stub代码的配置,本例子需要支持DSL,所以配置如下

//krotoPlusConfig.ascilibp
proto_builders {
    filter { exclude_path: "google/*" }
    unwrap_builders: true
    use_dsl_markers: true
}

最后,为了让生成代码参与编译,配置sourceSets指定codegen的目录

sourceSets {
    main {
        java {
            srcDir("$buildDir/generated/source/proto/main/java")
            srcDir("$buildDir/generated/source/proto/main/grpc")
            srcDir("$buildDir/generated/source/proto/main/coroutines")
        }
    }
}

2.protobuf定义


分别定义了四种服务类型的rpc

api.proto:

syntax = "proto3";
option java_multiple_files = true;
option java_package = "com.my.krotosample.protobuf";
option java_outer_classname = "KrotoSample";

package api;

message MessageRequest {
    string message = 1;
}

message MessageResponse {
    string message = 1;
}

service MessageService {
    rpc Unary (MessageRequest) returns (MessageResponse);
    rpc ClientStream (stream MessageRequest) returns (MessageResponse);
    rpc ServerStream (MessageRequest) returns (stream MessageResponse);
    rpc BidirectionalStream (stream MessageRequest) returns (stream MessageResponse);
}

3.codegen


gradle命令执行 generateProto

 ./gradlew generateProto

4.Server端实现


实现MessageServiceImpl,继承自MessageServiceCoroutineGrpc.MessageServiceImplBase

@ExperimentalCoroutinesApi
class MessageServiceImpl : MessageServiceCoroutineGrpc.MessageServiceImplBase() {
    override val initialContext: CoroutineContext
        get() = Dispatchers.Default + SupervisorJob()

    override suspend fun unary(
        request: MessageRequest
    ): MessageResponse {
        return MessageResponse {
            message = request.message.toUpperCase()
        }
    }

    override suspend fun clientStream(
        requestChannel: ReceiveChannel<MessageRequest>
    ): MessageResponse {
        val requestList = requestChannel.toList()
        return MessageResponse {
            message = requestList.joinToString("\n") {
                it.message.toUpperCase()
            }
        }
    }

    override suspend fun serverStream(
        request: MessageRequest,
        responseChannel: SendChannel<MessageResponse>
    ) {
        val response = MessageResponse {
            message = request.message.toUpperCase()
        }
        repeat(2) {
            responseChannel.send(response)
        }
        responseChannel.close()
    }

    override suspend fun bidirectionalStream(
        requestChannel: ReceiveChannel<MessageRequest>,
        responseChannel: SendChannel<MessageResponse>
    ) {
        requestChannel.consumeEach { request ->
            val response = MessageResponse {
                message = request.message.toUpperCase()
            }
            responseChannel.send(response)
        }
    }
}

作为例子,业务比较简单,将request携带的message进行upperCase后返回。
stream中使用了Coroutine的Channel,相对于回调的写法更加方便


5. 启动server


gradle启动server

./gradlew run

Main.kt:

@ExperimentalCoroutinesApi
fun main() {
    val port = 6565
    val server = ServerBuilder.forPort(port)
        .addService(MessageServiceImpl())
        .build()
        .start()
    Runtime.getRuntime().addShutdownHook(Thread() {
        server.shutdown()
    })
    server.awaitTermination()
}

build.gradle中配置main

application {
    mainClassName = "MainKt"
}

6.Client端实现


Client端的stub也可以支持Coroutine

Main.kt:

@ExperimentalCoroutinesApi
fun main() {
    val port = 6565
    val server = ServerBuilder.forPort(port)
        .addService(MessageServiceImpl())
        .build()
        .start()

    val channel = ManagedChannelBuilder.forAddress("localhost", 6565)
        .usePlaintext()
        .build()
    val client = MessageServiceCoroutineGrpc
        .MessageServiceCoroutineStub.newStub(channel)
    println("--- Bidirectional Stream start ---")
    runBlocking {
        val (requestChannel, responseChannel) = client.bidirectionalStream()
        listOf("hello", "kotlin", "proto").forEach {
            requestChannel.send {
                message = it
            }
        }
        requestChannel.close()
        responseChannel.consumeEach {
            println(it.message)
        }
    }
    println("--- Bidirectional Stream finish ---")
    println("--- Client Stream start ---")
    runBlocking {
        val (requestChannel, response) = client.clientStream()
        listOf("hello", "kotlin", "proto").forEach {
            requestChannel.send {
                message = it
            }
        }
        requestChannel.close()
        println(response.await().message)
    }
    println("--- Client Stream finish ---")
    println("--- Server Stream start ---")
    runBlocking {
        val request = MessageRequest {
            message = "kproto"
        }
        client.serverStream(request).consumeEach {
            println(it.message)
        }
    }
    println("--- Server Stream finish ---")
    server.shutdown()
}

Kproto+生成的stub都是suspend函数,所以可以在Coroutine中执行,同样使用Channel甚至Flow方便的进行IPC通信

运行结果

--- Bidirectional Stream start ---
HELLO
KOTLIN
PROTO
--- Bidirectional Stream finish ---
--- Client Stream start ---
HELLO
KOTLIN
PROTO
--- Client Stream finish ---
--- Server Stream start ---
KPROTO
KPROTO
--- Server Stream finish ---

你可能感兴趣的:(Kotlin)