Slide 1

Slide 1 text

grpc-kotlinはどこまでKotlin化できるのか? 2020年6月17日 Kotlin愛好会 vol.22 竹端 尚人

Slide 2

Slide 2 text

自己紹介

Slide 3

Slide 3 text

概要 氏名:竹端 尚人 Twitter:@n_takehata ・サーバーサイドKotlin ・(少し前まで)スマートフォンゲーム開発 ・今月転職して新しい会社で???を開発

Slide 4

Slide 4 text

登壇、執筆など ・CEDEC 2018登壇 ・Software Design 2019年2〜4月号で短期連載 ・Swift/Kotlin愛好会で技術書典執筆 https://booth.pm/ja/items/1315478

Slide 5

Slide 5 text

今日話すこと

Slide 6

Slide 6 text

grpc-java -> gprc-kotlin • 今年の4月にgrpc-kotlinがリリース • これまでKotlinでgRPCを使うにはgrpc-javaを使用していたが、 待望のKotlinのライブラリが登場 • ただし、まだ全てをKotlin化できるわけではない

Slide 7

Slide 7 text

grpc-kotlinを使うGradleの依存関係(抜粋) implementation "io.grpc:grpc-kotlin-stub:$grpc_kotlin_version" implementation "com.google.protobuf:protobuf-java:$protobuf_version" implementation "com.google.protobuf:protobuf-java-util:$protobuf_version" implementation "io.grpc:grpc-netty-shaded:$grpc_version" implementation "io.grpc:grpc-protobuf:$grpc_version" implementation "io.grpc:grpc-stub:$grpc_version" Javaのライブラリも併せて必要

Slide 8

Slide 8 text

コード生成のGradleタスク(抜粋) Javaのコード生成も必要 plugins { grpc { artifact = "io.grpc:protoc-gen-grpc-java:$grpc_version" } grpckt { artifact = "io.grpc:protoc-gen-grpc-kotlin:$grpc_kotlin_version" } }

Slide 9

Slide 9 text

gRPCの実装を どこまでKotlin化できるのかを紹介します

Slide 10

Slide 10 text

アジェンダ 1.どこをKotlin化できるのか? 2.Service 3.Stub 4.Javaのままの箇所 5.grpc-spring-boot-starterと組み合わせて使う

Slide 11

Slide 11 text

1.どこをKotlin化できるのか?

Slide 12

Slide 12 text

gRPC実装時の主な要素 • サーバー • Service • ServerInterceptor • クライアント • Channel • Stub • ClientInterceptor • 共通 • message

Slide 13

Slide 13 text

gRPC実装時の主な要素 • サーバー • Service • ServerInterceptor • クライアント • Channel • Stub • ClientInterceptor • 共通 • message

Slide 14

Slide 14 text

Service、Stubの部分がKotlin化できる

Slide 15

Slide 15 text

2.Service

Slide 16

Slide 16 text

実装比較

Slide 17

Slide 17 text

grpc-javaでの実装(抜粋) class GreeterImpl : GreeterGrpc.GreeterImplBase() { override fun sayHello(req: HelloRequest, responseObserver: StreamObserver) { val reply = HelloReply.newBuilder().setMessage("Hello ${req.name}").build() responseObserver.onNext(reply) responseObserver.onCompleted() } } Observerパターンで実装されている

Slide 18

Slide 18 text

grpc-kotlinでの実装(抜粋) class HelloWorldService : GreeterGrpcKt.GreeterCoroutineImplBase() { override suspend fun sayHello(request: HelloRequest) = HelloReply .newBuilder() .setMessage("Hello ${request.name}") .build() } ・親クラスをKotlin化したものに変更 ・suspend関数に変更 ・Observerを使わなくなった

Slide 19

Slide 19 text

ただし

Slide 20

Slide 20 text

CoroutineServiceの親クラス(抜粋) Javaのクラスを継承している(Javaに依存している) abstract class AbstractCoroutineServerImpl( /** The context in which to run server coroutines. */ val context: CoroutineContext = EmptyCoroutineContext ) : BindableService {

Slide 21

Slide 21 text

3.Stub

Slide 22

Slide 22 text

実装比較

Slide 23

Slide 23 text

grpc-javaでの実装(抜粋) fun greet(name: String) { val blockingStub: GreeterGrpc.GreeterBlockingStub = GreeterGrpc.newBlockingStub(channel) val request = HelloRequest.newBuilder().setName(name).build() val response: HelloReply = blockingStub.sayHello(request) logger.info("Greeting: ${response.message}") } Stubを生成して実行

Slide 24

Slide 24 text

grpc-kotlinでの実装(抜粋) suspend fun greet(name: String) = coroutineScope { val stub: GreeterCoroutineStub = GreeterCoroutineStub(channel) val request = HelloRequest.newBuilder().setName(name).build() val response = async { stub.sayHello(request) } println("Received: ${response.await().message}") } ・Kotlin化されたStubを使う ・Stubの関数もsuspend関数になっているため、非同期で実行する

Slide 25

Slide 25 text

ただし

Slide 26

Slide 26 text

Stubの親クラス abstract class AbstractCoroutineStub>( channel: Channel, callOptions: CallOptions = CallOptions.DEFAULT ): AbstractStub(channel, callOptions) Serviceと同様Javaのクラスを継承している(Javaに依存している)

Slide 27

Slide 27 text

4.Javaのままの箇所

Slide 28

Slide 28 text

Javaのままの箇所 • ServerInterceptor • ClientInterceptor • Channel • message

Slide 29

Slide 29 text

ServerInterceptor @GRpcGlobalInterceptor @Order(10) class LoggingInterceptor : ServerInterceptor { companion object { private val log = LoggerFactory.getLogger(LoggingInterceptor::class.java) } override fun interceptCall(call: ServerCall, headers: Metadata, next: ServerCallHandler): ServerCall.Listener { log.info("method name=${call.methodDescriptor.fullMethodName}") return next.startCall(call, headers) } }

Slide 30

Slide 30 text

ClientIterceptor class MetadataClientInterceptor : ClientInterceptor { companion object { private val log = LoggerFactory.getLogger(MetadataClientInterceptor::class.java) } override fun interceptCall(method: MethodDescriptor?, callOptions: CallOptions?, next: Channel): ClientCall? { return object : SimpleForwardingClientCall(next.newCall(method, callOptions)) { override fun start(responseListener: Listener?, headers: Metadata) { headers.put(Metadata.Key.of(“example”, Metadata.ASCII_STRING_MARSHALLER), “Example”) // ・・・

Slide 31

Slide 31 text

Channel、message suspend fun greet(name: String) = coroutineScope { val channel = ManagedChannelBuilder.forAddress("localhost", 6565) .usePlaintext().build() val stub: GreeterCoroutineStub = GreeterCoroutineStub(channel) val request = HelloRequest.newBuilder().setName(name).build() val response = async { stub.sayHello(request) } println("Received: ${response.await().message}") }

Slide 32

Slide 32 text

messageをもしデータクラスにできたら・・・ suspend fun greet(name: String) = coroutineScope { val channel = ManagedChannelBuilder.forAddress("localhost", 6565) .usePlaintext().build() val stub: GreeterCoroutineStub = GreeterCoroutineStub(channel) val request = HelloRequest (name) val response = async { stub.sayHello(request) } println("Received: ${response.await().message}") }

Slide 33

Slide 33 text

5.grpc-spring-boot-starterと組み合わせて使う

Slide 34

Slide 34 text

Gradleの依存関係

Slide 35

Slide 35 text

grpc-kotlinを使うGradleの依存関係(再掲) implementation "io.grpc:grpc-kotlin-stub:$grpc_kotlin_version" implementation "com.google.protobuf:protobuf-java:$protobuf_version" implementation "com.google.protobuf:protobuf-java-util:$protobuf_version" implementation "io.grpc:grpc-netty-shaded:$grpc_version" implementation "io.grpc:grpc-protobuf:$grpc_version" implementation "io.grpc:grpc-stub:$grpc_version"

Slide 36

Slide 36 text

spring-boot-starterを使う場合の依存関係(抜粋) implementation "org.springframework.boot:spring-boot-starter-webflux” implementation "io.github.lognet:grpc-spring-boot-starter:$grpc_spring_boot_version" implementation "io.grpc:grpc-kotlin-stub:$grpc_kotlin_version”

Slide 37

Slide 37 text

サーバーの実装比較

Slide 38

Slide 38 text

grpc-javaでの実装(抜粋) @GRpcService(interceptors = [MessageInterceptor::class]) class ExampleGrpcService : ExampleGrpc.ExampleImplBase() { override fun createMessage(request: CreateMessageRequest, responseObserver: StreamObserver) { // ・・・

Slide 39

Slide 39 text

grpc-kotlinでの実装(抜粋) @GRpcService(interceptors = [MessageInterceptor::class]) class ExampleGrpcService : ExampleGrpcKt.ExampleCoroutineImplBase() { override suspend fun createMessage(request: CreateMessageRequest): CreateMessageResponse { // ・・・ もともとの実装にgrpc-kotlinを組み込んだ形

Slide 40

Slide 40 text

クライアントの実装比較 (Controllerからの呼び出しを例に)

Slide 41

Slide 41 text

grpc-javaでの実装(抜粋) @RequestMapping("/createmessage") fun createMessage(@RequestBody request: RestCreateMessageRequest): String { val createMessageRequest = CreateMessageRequest.newBuilder().setName(request.name).build() val channel = ManagedChannelBuilder.forAddress("localhost", 6565) .usePlaintext().build() val stub = ExampleGrpc.newBlockingStub(channel) val response = stub.createMessage(createMessageRequest) return response.message.text }

Slide 42

Slide 42 text

grpc-kotlinでの実装(抜粋) @PostMapping("/createmessage") suspend fun createMessage(@RequestBody request: RestCreateMessageRequest): String { val createMessageRequest = CreateMessageRequest.newBuilder().setName(request.name).build() val channel = ManagedChannelBuilder.forAddress("localhost", 6565) .usePlaintext().build() val stub = ExampleGrpcKt.ExampleCoroutineStub(channel) val response = async { stub.createMessage(createMessageRequest) } return response.await().message.text }

Slide 43

Slide 43 text

• Stubの関数がsuspend関数なので、Controllerまでsuspendが連鎖する • 通常のSpring BootではControllerをsuspend関数にできず、 runBlockingなどする必要がある

Slide 44

Slide 44 text

spring-boot-starterを使う場合の依存関係(抜粋) implementation "org.springframework.boot:spring-boot-starter-webflux” implementation "io.github.lognet:grpc-spring-boot-starter:$grpc_spring_boot_version" implementation "io.grpc:grpc-kotlin-stub:$grpc_kotlin_version” Spring WebFluxを使うと非同期I/Oが実現できる

Slide 45

Slide 45 text

KotlinでSpring Boot使うなら WebFluxにしたい

Slide 46

Slide 46 text

まとめ

Slide 47

Slide 47 text

• Service、Stubに関してはKotlin化できる • ただし親クラスがJavaだったり、Javaのライブラリを呼んでいたりと依存 はしている • spring-boot-starterとの組み合わせは問題なさそう

Slide 48

Slide 48 text

https://blog.takehata-engineer.com/entry/change-to-kotlin- by-grpc-kotlin こちらのブログもご御覧ください grpc-kotlinはgRPCの実装をどこまでKotlin化できるのか? grpc-kotlinをgrpc-spring-boot-starterと組み合わせて使う https://blog.takehata-engineer.com/entry/grpc-kotlin-with- grpc-spring-boot-starter

Slide 49

Slide 49 text

ご清聴ありがとうございました