Upgrade to Pro — share decks privately, control downloads, hide ads and more …

gRPC with Kotlin Coroutines

Mohit S
August 05, 2020

gRPC with Kotlin Coroutines

gRPC is a technology that allows you to call server side logic from any platform using protocol buffers. What are the libraries available that allow you to create and consume gRPC services using coroutines? What are the debugging and monitoring tools we could use for gRPC?

In this talk, I will share with you how to build a gRPC server using the gRPC-Kotlin library. We’ll explore how to use protocol buffers to define different types of rpc calls that are unary and bidirectional. On the client side, we’ll also use this library to consume our gRPC service. gRPC-Kotlin provides an API that uses Flows to make rpc calls. We’ll explore how it works internally. For each rpc call we implement using coroutines, I’ll show you how to unit test it.

In addition to gRPC Kotlin, other libraries for creating and consuming gRPC services are Wire by Square and Kroto-plus. We’ll compare its features and its coroutines API with gRPC-Kotlin. By the end of this talk, you will have a better understanding on how to build and consume gRPC services with Kotlin coroutines.

https://www.youtube.com/watch?v=V3BzDyQVeGw

Mohit S

August 05, 2020
Tweet

More Decks by Mohit S

Other Decks in Technology

Transcript

  1. gRPC with Kotlin Coroutines • Build gRPC Server • Using

    Channels & Flow with gRPC • Build gRPC Client in Kotlin
  2. Use Case • Tracks your route. • Get a place

    from location. • List Places around location.
  3. Use Case • Tracks your route. • Get a place

    from location. • List Places around location. • Chat with others at location. Shoreline Golf links 2940 N Shoreline Blvd Mountain View, CA 94043 Start typing
  4. Building gRPC Server • Configure Server with Kroto-Plus • Create

    Service with Protocol Buffers • Implement Service • Start Server
  5. Unary RPC Call syntax = “proto3”; service Places { rpc

    GetPlace(Location) returns (Place) {}; } Unary RPC Call
  6. Unary RPC Call syntax = “proto3”; service Places { rpc

    GetPlace(Location) returns (Place) {}; keyword
  7. Unary RPC Call syntax = “proto3”; service Places { rpc

    GetPlace(Location) returns (Place) {}; } Name of RPC call
  8. Unary RPC Call syntax = “proto3”; service Places { rpc

    GetPlace(Location) returns (Place) {}; } Take a Location
  9. Unary RPC Call syntax = “proto3”; service Places { rpc

    GetPlace(Location) returns (Place) {}; } Returns Place
  10. Unary RPC Call syntax = “proto3”; service Places { rpc

    GetPlace(Location) returns (Place) {}; } Unary RPC Call
  11. Unary RPC Call syntax = “proto3”; service Places { rpc

    GetPlace(Location) returns (Place) {}; How do we define our messages?
  12. Unary RPC Call service Places { message Location { double

    latitude = 1; double longitude = 2; } } Location message
  13. Unary RPC Call service Places { message Location { double

    latitude = 1; double longitude = 2; } } Keyword
  14. Unary RPC Call service Places { message Location { double

    latitude = 1; double longitude = 2; } } Message name
  15. Unary RPC Call service Places { message Location { double

    latitude = 1; double longitude = 2; } Fields
  16. Unary RPC Call service Places { message Location { double

    latitude = 1; double longitude = 2; } Type
  17. Unary RPC Call service Places { message Location { double

    latitude = 1; double longitude = 2; } Type .proto Type C++ Java Go double double Double *float64 int32 int32 int *int32 Int64 long int/long *int64 https://developers.google.com/protocol-buffers/docs/overview#scalar
  18. Unary RPC Call service Places { message Location { double

    latitude = 1; double longitude = 2; } Field numbers
  19. Unary RPC Call syntax = “proto3”; service Places { rpc

    GetPlace(Location) returns (Place) {}; } How do we define Place message?
  20. Unary RPC Call message Place { string name = 1;

    Location location = 2; } Name & Location
  21. Unary RPC Call message Place { string name = 1;

    Location location = 2; 
 PlaceType placeType = 3; } Enum
  22. Unary RPC Call message Place { string name = 1;

    Location location = 2; 
 PlaceType placeType = 3; enum PlaceType { Landmark = 0; Driving_Range = 1; Golf_Course = 2; Restaurant = 3; Retail = 4; } Enum
  23. Unary RPC Call message Place { string name = 1;

    Location location = 2; PlaceType placeType = 3; enum PlaceType { … } int64 checkins = 4; int64 comments = 5; } Int Scaler Types
  24. Unary RPC Call syntax = “proto3”; service Places { rpc

    GetPlace(Location) returns (Place) {}; } Unary RPC Call
  25. Unary RPC Call syntax = “proto3”; service Places { rpc

    CheckIn(Place) returns () {}; } Check in to place
  26. Unary RPC Call syntax = “proto3”; service Places { rpc

    CheckIn(Place) returns () {}; } How do we return an empty?
  27. Unary RPC Call syntax = “proto3”; import "google/protobuf/empty.proto"; service Places

    { rpc CheckIn(Place) returns (google.protobuf.Empty) {}; } Empty
  28. Unary RPC Call syntax = “proto3”; import "google/protobuf/empty.proto"; service Places

    { rpc CheckIn(Place) returns (google.protobuf.Empty) {}; }
  29. Bidirectional RPC Call service Places { rpc Chat(stream Comment) returns

    (stream Comment) {}; } Bidirectional RPC Call
  30. RPC Call Types service Places { rpc GetPlace(Location) returns (Place)

    {}; rpc ListPlaces(Area) returns (stream Place) {}; rpc CheckIn(Place) returns (google.protobuf.Empty) {}; rpc Chat(stream Comment) returns (stream Comment) {}; }
  31. Building gRPC Server • Configure Server with Kroto-Plus • Create

    Service with Protocol Buffers • Implement Service • Start Server
  32. Creating Messages message Location { double latitude = 1; double

    longitude = 2; } class Location { static class Builder { Builder setLatitude(double value) Builder setLongitude(double value) Location build() } }
  33. Creating Messages message Location { double latitude = 1; double

    longitude = 2; } Location.newBuilder() .setLatitude(40.9888341) .setLongitude(-73.8502007) .build()
  34. Creating Messages message Location { double latitude = 1; double

    longitude = 2; } inline fun Location( block: Location.Builder.() "-> Unit ): Location = Location.newBuilder() .apply(block) .build()
  35. Creating Messages message Location { double latitude = 1; double

    longitude = 2; } Location { longitude = 40.9888341 latitude = -73.8502007 }
  36. gRPC with Coroutines api src / krotoPlusConfig.yml grpcCoroutines: [{}] protoBuilders:

    - unwrapBuilders: true - useDslMarkers: true Generate Coroutines
  37. Server Streaming abstract class PlacesImplBase rpc ListPlaces(Area) returns (stream Place)

    {}; suspend fun listPlaces( request: Area, responseChannel: SendChannel<Place> )
  38. Server Streaming abstract class PlacesImplBase rpc ListPlaces(Area) returns (stream Place)

    {}; suspend fun listPlaces( request: Area, responseChannel: SendChannel<Place> )
  39. Server Streaming abstract class PlacesImplBase rpc ListPlaces(Area) returns (stream Place)

    {}; suspend fun listPlaces( request: Area, responseChannel: SendChannel<Place> )
  40. Server Streaming abstract class PlacesImplBase rpc ListPlaces(Area) returns (stream Place)

    {}; suspend fun listPlaces( request: Area, responseChannel: SendChannel<Place> )
  41. Bidirectional abstract class PlacesImplBase rpc Chat(stream Comment) returns (stream Comment)

    {}; suspend fun chat( requestChannel: ReceiveChannel<Comment>, responseChannel: SendChannel<Comment> )
  42. Bidirectional abstract class PlacesImplBase rpc Chat(stream Comment) returns (stream Comment)

    {}; suspend fun chat( requestChannel: ReceiveChannel<Comment>, responseChannel: SendChannel<Comment> )
  43. Bidirectional abstract class PlacesImplBase rpc Chat(stream Comment) returns (stream Comment)

    {}; suspend fun chat( requestChannel: ReceiveChannel<Comment>, responseChannel: SendChannel<Comment> )
  44. Generate Service abstract class PlacesImplBase { open suspend fun getPlace(…):

    Place open suspend fun listPlaces(…) open suspend fun chat(…) open suspend fun recordTrip(…) }
  45. Implement Service class PlacesService(dispatcher): PlacesImplBase() { override suspend fun getPlace(request:

    Location): Place { val placeFromDB = getPlacesFromDb() .first { it.location "== request } } } Get Place from DB
  46. Implement Service class PlacesService(dispatcher): PlacesImplBase() { override suspend fun getPlace(request:

    Location): Place { val placeFromDB = getPlacesFromDb() .first { it.location "== request } return Place { name = placeFromDb.name !!... } } Map it to Proto Message
  47. Implement Service abstract class PlacesImplBase rpc ListPlaces(Area) returns (stream Place)

    {}; suspend fun listPlaces( request: Area, responseChannel: SendChannel<Place> )
  48. Implement Service abstract class PlacesImplBase suspend fun listPlaces( area: Area,

    responseChannel: SendChannel<Place> ) { val places = getPlaces(area) places.forEach { responseChannel.send(it) } } } Send to Channel
  49. Implement Service abstract class PlacesImplBase suspend fun listPlaces( area: Area,

    responseChannel: SendChannel<Place> ) { val places = getPlaces(area) places.forEach { responseChannel.send(it) } } } How does channel close?
  50. Errors abstract class PlacesImplBase suspend fun listPlaces( area: Area, responseChannel:

    SendChannel<Place> ) { val places = getPlaces(area) places.forEach { responseChannel.send(it) } } } Exception occurs?
  51. Implement Service class PlacesService(dispatcher): PlacesImplBase { suspend fun getPlace(…): Place

    suspend fun listPlaces(…) suspend fun chat(…) suspend fun recordTrip(…) }
  52. Building gRPC Server • Configure Server with Kroto-Plus • Create

    Service with Protocol Buffers • Implement Service • Start Server
  53. Start gRPC Server fun main() { val port = 50051

    val server = configureServer(port) server.start() server.blockUntilShutdown() }
  54. Building gRPC Server • Configure Server with Kroto-Plus • Create

    Service with Protocol Buffers • Implement Service • Start Server
  55. Resources • gRPC Java https:!//github.com/grpc/grpc-java • Kroto-plus https:!//github.com/marcoferrer/kroto-plus • Protocol

    Buffers https:!//developers.google.com/protocol-buffers/docs/overview • Micronaut https:!//micronaut.io/
  56. grpc/grpc-kotlin gRPC-Kotlin/JVM - An RPC library and framework grpc-kotlin-stub v0.1.4

    protoc-gen-grpc-kotlinstub v0.1.4 grpc-kotlin-stub-lite v0.1.4 A Kotlin/JVM implementation of gRPC: A high performance, open source, general RPC framework that puts mobile and HTTP/2 first. Bazel Build passing Gradle Build passing
  57. Generated Client class CoroutineStub { } rpc GetPlace(Location) returns (Place)

    {}; suspend fun getPlace(request: Location): Place
  58. Generated Client class CoroutineStub { } rpc ListPlaces(Area) returns (stream

    Place) {}; fun listPlaces(request: Area): Flow<Place> Stream is map to Flow
  59. Generated Client class CoroutineStub { } rpc Chat(stream Comment) returns

    (stream Comment) {}; fun chat(requests: Flow<Comment>): Flow<Comment> Stream is map to Flow
  60. Generated Client class CoroutineStub { } suspend fun getPlace(request: Location):

    Place fun recordTrip(requests: Flow<Location>): TripSummary fun listPlaces(request: Area): Flow<Place> fun chat(requests: Flow<Comment>): Flow<Comment>
  61. Using Client val managedChannel = ManagedChannelBuilder .forAddress(host, port) .intercept(object :

    ClientInterceptor { fun interceptCall(method, callOptions, channel) { }) .build() Intercept RPC Calls
  62. Using Client class GrpcViewModel(val client: CoroutineStub): ViewModel() { fun getPlace(location:

    Location) { viewModelScope.launch { val place = client.getPlace(location) } } }
  63. Using Client class GrpcViewModel(val client: CoroutineStub): ViewModel() { fun listPlaces(area:

    Area) { viewModelScope.launch { val places: Flow<Place> = client.listPlaces(area) places.collect { } } } }
  64. Using Client class GrpcViewModel(val client: CoroutineStub): ViewModel() { fun chat(comments:

    ReceiveChannel<Comment>) { viewModelScope.launch { client.chat(comments.consumeAsFlow()).collect { } } } } Stream of comments
  65. Using Client class GrpcViewModel(val client: CoroutineStub): ViewModel() { fun chat(comments:

    ReceiveChannel<Comment>) { viewModelScope.launch { client.chat(comments.consumeAsFlow()) .collect { } } } } Convert Channel to Flow
  66. Using Client class GrpcViewModel(val client: CoroutineStub): ViewModel() { fun chat(comments:

    ReceiveChannel<Comment>) { viewModelScope.launch { client.chat(comments.consumeAsFlow()) .collect { } } } } Collect from Flow
  67. Using Client class GrpcViewModel(val client: CoroutineStub): ViewModel() { fun getPlace(location:

    Location) fun listPlaces(area: Area) fun chat(chat: ReceiveChannel<Comment>) }
  68. Resources • Unit Testing Delays, Errors & Retries with Kotlin

    Flows https:!//codingwithmohit.com/coroutines/unit-testing-delays- errors-retries-with-kotlin-flows/ • Kotlin Assert Flow Delight https:!//codingwithmohit.com/coroutines/kotlin-assert-flow-delight/ • Channels & Flows in Practice https:!//speakerdeck.com/heyitsmohit/channels-and-flows-in-practice