Wire 3 : Tackling gRPC with Kotlin

Wire 3 : Tackling gRPC with Kotlin

Video: https://youtu.be/xo_zVPAXbBg

Co-Presented by @egorand and @oldergod.
Protocol Buffers (or Protobuf) is an efficient schema-based data serialization protocol, and gRPC is a high-performance, HTTP/2-based RPC framework. The two work together flawlessly to help you build world class distributed systems.

At Square, client and server engineers collaborate on Protobuf schemas to define APIs. We also built Wire - a library which processes schemas and generates Java code that applications can use to send and receive data. Last year we started working on Wire 3, which is rewritten in Kotlin, generates Kotlin code, and adds a number of exciting features:

- Protobuf messages as data classes
- Multiplatform runtime module
- Coroutines-based gRPC APIs
- Gradle plugin

In this session, we’ll take a deep dive into these features, and talk about how we leveraged Kotlin to create better APIs. We’ll show you how to get the best out of Protobuf and gRPC for your server and client applications using Wire.

05162bc961c3654218bf1839974a4f35?s=128

Benoît Quenaudon

August 27, 2019
Tweet

Transcript

  1. Tackling gRPC with Kotlin WIRE 3 @oldergod @egorand

  2. None
  3. None
  4. Why gRPC?

  5. Service definition language Protocol Buffers Cross platform/language HTTP/2 superpowers

  6. Protocol Buffers

  7. Schema-based Rich tooling support Lightweight on the wire Strong backwards

    compatibility guarantees
  8. Schema

  9. syntax = "proto2"; package com.squareup; option java_package = "com.squareup"; message

    Person { required int32 id = 1; optional string name = 2; optional Occupation occupation = 3; enum Occupation { SOFTWARE_DEVELOPER = 0; STUDENT = 1; PRESIDENT = 2; } }
  10. syntax = "proto2"; package com.squareup; option java_package = "com.squareup"; message

    Person { required int32 id = 1; optional string name = 2; optional Occupation occupation = 3; enum Occupation { SOFTWARE_DEVELOPER = 0; STUDENT = 1; PRESIDENT = 2; } } Syntax proto2 proto3 Package Options java_package custom Messages Enums Tags
  11. syntax = "proto2"; package com.squareup; option java_package = "com.squareup"; message

    Person { required int32 id = 1; optional string name = 2; optional Occupation occupation = 3; enum Occupation { SOFTWARE_DEVELOPER = 0; STUDENT = 1; PRESIDENT = 2; } } Syntax proto2 proto3 Package Options java_package custom Messages Enums Tags int 1 to 536,870,911 Field rules
  12. syntax = "proto2"; package com.squareup; option java_package = "com.squareup"; message

    Person { required int32 id = 1; optional string name = 2; optional Occupation occupation = 3; enum Occupation { SOFTWARE_DEVELOPER = 0; STUDENT = 1; PRESIDENT = 2; } } Package Options java_package custom Messages Enums Tags int 1 to 536,870,911 Field rules required optional repeated
  13. syntax = "proto2"; package com.squareup; option java_package = "com.squareup"; message

    Person { required int32 id = 1; optional string name = 2; optional Occupation occupation = 3; enum Occupation { SOFTWARE_DEVELOPER = 0; STUDENT = 1; PRESIDENT = 2; } } java_package custom Messages Enums Tags int 1 to 536,870,911 Field rules required optional repeated Field types double float int32 int64 uint32
  14. syntax = "proto2"; package com.squareup; option java_package = "com.squareup"; message

    Person { required int32 id = 1; optional string name = 2; optional Occupation occupation = 3; enum Occupation { SOFTWARE_DEVELOPER = 0; STUDENT = 1; PRESIDENT = 2; } } Enums Tags int 1 to 536,870,911 Field rules required optional repeated Field types double float int32 int64 uint32 uint64 sint32 sint64 fixed32 fixed64 sfixed32 sfixed64 bool string bytes
  15. syntax = "proto2"; package com.squareup; option java_package = "com.squareup"; message

    Person { required int32 id = 1; optional string name = 2; optional Occupation occupation = 3; enum Occupation { SOFTWARE_DEVELOPER = 0; STUDENT = 1; PRESIDENT = 2; } } Tags int 1 to 536,870,911 Field rules required optional repeated Field types double float int32 int64 uint32 uint64 sint32 sint64 fixed32 fixed64 sfixed32 sfixed64 bool string bytes
  16. syntax = "proto2"; package com.squareup; option java_package = "com.squareup"; message

    Person { required int32 id = 1; optional string name = 2; optional Occupation occupation = 3; enum Occupation { SOFTWARE_DEVELOPER = 0; STUDENT = 1; PRESIDENT = 2; } } Field rules required optional repeated Field types double float int32 int64 uint32 uint64 sint32 sint64 fixed32 fixed64 sfixed32 sfixed64 bool string bytes
  17. Bytes

  18. syntax = "proto2"; package com.squareup; option java_package = "com.squareup"; message

    Person { required int32 id = 1; optional string name = 2; optional Occupation occupation = 3; enum Occupation { SOFTWARE_DEVELOPER = 0; STUDENT = 1; PRESIDENT = 2; } }
  19. syntax = "proto2"; package com.squareup; option java_package = "com.squareup"; message

    Person { required int32 id = 1; optional string name = 2; optional Occupation occupation = 3; enum Occupation { SOFTWARE_DEVELOPER = 0; STUDENT = 1; PRESIDENT = 2; } } val person = Person( id = 23, name = "egor", occupation = PRESIDENT ) val bytes = Person.ADAPTER.encode(person) println(bytes.toByteString().hex()) // 0817120465676f721802
  20. "proto2"; com.squareup; ava_package = "com.squareup"; Person { ed int32 id

    = 1; al string name = 2; al Occupation occupation = 3; ccupation { WARE_DEVELOPER = 0; ENT = 1; IDENT = 2; 0817120465676f721802
  21. 08 17 12 04 65 67 6f 72 18 02

    "proto2"; com.squareup; ava_package = "com.squareup"; Person { ed int32 id = 1; al string name = 2; al Occupation occupation = 3; ccupation { WARE_DEVELOPER = 0; ENT = 1; IDENT = 2; == 10 bytes
  22. syntax = "proto2"; package com.squareup; option java_package = "com.squareup"; message

    Person { required int32 id = 1; optional string name = 2; optional Occupation occupation = 3; enum Occupation { SOFTWARE_DEVELOPER = 0; STUDENT = 1; PRESIDENT = 2; } } 08 17 12 04 65 67 6f 72
  23. syntax = "proto2"; package com.squareup; option java_package = "com.squareup"; message

    Person { required int32 id = 1; optional string name = 2; optional Occupation occupation = 3; enum Occupation { SOFTWARE_DEVELOPER = 0; STUDENT = 1; PRESIDENT = 2; } } 08 17 12 Tag: id(1) Field encoding: VARINT(0) 0000 0001 << 3 0000 1000 | 0 0000 1000 == 08
  24. syntax = "proto2"; package com.squareup; option java_package = "com.squareup"; message

    Person { required int32 id = 1; optional string name = 2; optional Occupation occupation = 3; enum Occupation { SOFTWARE_DEVELOPER = 0; STUDENT = 1; PRESIDENT = 2; } } 08 17 12 04 65 67 6f 72 18 id = 23
  25. syntax = "proto2"; package com.squareup; option java_package = "com.squareup"; message

    Person { required int32 id = 1; optional string name = 2; optional Occupation occupation = 3; enum Occupation { SOFTWARE_DEVELOPER = 0; STUDENT = 1; PRESIDENT = 2; } } 08 17 12 04 Tag: name(2) Field encoding: LENGTH_DELIMITED(2) 0000 0010 << 3 0001 0000 | 2 0000 1010 == 12
  26. syntax = "proto2"; package com.squareup; option java_package = "com.squareup"; message

    Person { required int32 id = 1; optional string name = 2; optional Occupation occupation = 3; enum Occupation { SOFTWARE_DEVELOPER = 0; STUDENT = 1; PRESIDENT = 2; } } 17 12 04 65 67 6f 72 18 02 “egor”.length()
  27. syntax = "proto2"; package com.squareup; option java_package = "com.squareup"; message

    Person { required int32 id = 1; optional string name = 2; optional Occupation occupation = 3; enum Occupation { SOFTWARE_DEVELOPER = 0; STUDENT = 1; PRESIDENT = 2; } } 17 12 04 65 67 6f 72 18 02 “egor”.length() ‘e’ ‘g’ ‘o’ ‘r’
  28. syntax = "proto2"; package com.squareup; option java_package = "com.squareup"; message

    Person { required int32 id = 1; optional string name = 2; optional Occupation occupation = 3; enum Occupation { SOFTWARE_DEVELOPER = 0; STUDENT = 1; PRESIDENT = 2; } } 6f 72 18 02 Tag: occupation(3) Field encoding: VARINT(0) 0000 0011 << 3 0001 1000 | 0 0001 1000 == 18
  29. syntax = "proto2"; package com.squareup; option java_package = "com.squareup"; message

    Person { required int32 id = 1; optional string name = 2; optional Occupation occupation = 3; enum Occupation { SOFTWARE_DEVELOPER = 0; STUDENT = 1; PRESIDENT = 2; } } 72 18 02 occupation = PRESIDENT
  30. Backwards compatibility

  31. message Person { required int32 id = 1; optional string

    name = 2; optional Occupation occupation = 3; enum Occupation { SOFTWARE_DEVELOPER = 0; STUDENT = 1; PRESIDENT = 2; } } v1
  32. message Person { required int32 id = 1; optional string

    name = 2; optional Occupation occupation = 3; enum Occupation { SOFTWARE_DEVELOPER = 0; STUDENT = 1; PRESIDENT = 2; } } v1 val person = Person( id = 23, name = "egor", occupation = PRESIDENT ) val v1 = Person.ADAPTER .encode(person) .toByteString().hex() // 0817120465676f721802
  33. v2 message Person { required int32 id = 1; optional

    string name = 2; optional Occupation occupation = 3; required int32 age = 4; enum Occupation { SOFTWARE_DEVELOPER = 0; STUDENT = 1; PRESIDENT = 2; } } + // 0817120465676f721802 val personV2 = Person.ADAPTER .decode( “0817120465676f721802" .decodeHex() ) println(personV2) Exception in thread "main" java.lang.IllegalStateException: Required field not set: age
  34. v2 message Person { required int32 id = 1; optional

    string name = 2; optional Occupation occupation = 3; optional int32 age = 4; enum Occupation { SOFTWARE_DEVELOPER = 0; STUDENT = 1; PRESIDENT = 2; } } + // 0817120465676f721802 val personV2 = Person.ADAPTER .decode( “0817120465676f721802" .decodeHex() ) println(personV2) Person{id=23, name=egor, occupation=PRESIDENT}
  35. v2 message Person { required int32 id = 1; optional

    string name = 2; optional Occupation occupation = 3; enum Occupation { SOFTWARE_DEVELOPER = 0; STUDENT = 1; PRESIDENT = 2; } } - // 0817120465676f721802 val personV2 = Person.ADAPTER .decode( “0817120465676f721802" .decodeHex() ) println(personV2) Person{name=egor, occupation=PRESIDENT}
  36. v2 message Person { required int32 id = 1; optional

    string name = 2; optional Occupation occupation = 3; enum Occupation { SOFTWARE_DEVELOPER = 0; STUDENT = 1; PRESIDENT = 2; } } - // 0817120465676f721802 val personV2 = Person.ADAPTER .decode( “0817120465676f721802" .decodeHex() ) println(personV2) Person{id=23, occupation=PRESIDENT}
  37. v2 message Person { required int32 id = 1; optional

    string name = 2; optional string name = 4; optional Occupation occupation = 3; enum Occupation { SOFTWARE_DEVELOPER = 0; STUDENT = 1; PRESIDENT = 2; } } - + // 0817120465676f721802 val personV2 = Person.ADAPTER .decode( “0817120465676f721802" .decodeHex() ) println(personV2) Person{id=23, occupation=PRESIDENT}
  38. v2 message Person { required int32 id = 1; optional

    string name = 2; optional string name = 3; optional Occupation occupation = 3; enum Occupation { SOFTWARE_DEVELOPER = 0; STUDENT = 1; PRESIDENT = 2; } } - + - // 0817120465676f721802 val personV2 = Person.ADAPTER .decode( “0817120465676f721802" .decodeHex() ) println(personV2) Exception in thread "main" java.net.ProtocolException: Expected LENGTH_DELIMITED but was 0
  39. v2 message Person { required int32 id = 1; optional

    string name = 2; optional float name = 2; optional Occupation occupation = 3; enum Occupation { SOFTWARE_DEVELOPER = 0; STUDENT = 1; PRESIDENT = 2; } } - + // 0817120465676f721802 val personV2 = Person.ADAPTER .decode( “0817120465676f721802" .decodeHex() ) println(personV2) Person{id=23, name=4.7418825E30, occupation=PRESIDENT}
  40. v2 message Person { required int32 id = 1; optional

    string name = 2; optional string maName = 2; optional Occupation occupation = 3; enum Occupation { SOFTWARE_DEVELOPER = 0; STUDENT = 1; PRESIDENT = 2; } } - + // 0817120465676f721802 val personV2 = Person.ADAPTER .decode( “0817120465676f721802" .decodeHex() ) println(personV2) Person{id=23, maName=egor, occupation=PRESIDENT}
  41. Supported languages Java C# C++ Python Objective-C JavaScript Ruby Go

    Dart PHP
  42. Why WIRE ?

  43. message Person { required int32 id = 1; optional string

    name = 2; optional Occupation occupation = 3; enum Occupation { SOFTWARE_DEVELOPER = 0; STUDENT = 1; PRESIDENT = 2; } }
  44. None
  45. // Generated by the protocol buffer compiler. DO NOT EDIT!

    // source: com/squareup/person.proto package com.squareup; public final class PersonOuterClass { private PersonOuterClass() {} public static void registerAllExtensions( com.google.protobuf.ExtensionRegistryLite registry) { } public static void registerAllExtensions( com.google.protobuf.ExtensionRegistry registry) { registerAllExtensions( (com.google.protobuf.ExtensionRegistryLite) registry); } public interface PersonOrBuilder extends // @@protoc_insertion_point(interface_extends:com.squareup.Person) com.google.protobuf.MessageOrBuilder { /** * <code>required int32 id = 1;</code> */ boolean hasId(); /** * <code>required int32 id = 1;</code> */
  46. */ public static final class Person extends com.google.protobuf.GeneratedMessageV3 implements //

    @@protoc_insertion_point(message_implements:com.squareup.Person) PersonOrBuilder { private static final long serialVersionUID = 0L; // Use Person.newBuilder() to construct. private Person(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) { super(builder); } private Person() { name_ = ""; occupation_ = 0; } @java.lang.Override public final com.google.protobuf.UnknownFieldSet getUnknownFields() { return this.unknownFields; } private Person( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { this(); if (extensionRegistry == null) { throw new java.lang.NullPointerException(); }
  47. while (!done) { int tag = input.readTag(); switch (tag) {

    case 0: done = true; break; case 8: { bitField0_ |= 0x00000001; id_ = input.readInt32(); break; } case 18: { com.google.protobuf.ByteString bs = input.readBytes(); bitField0_ |= 0x00000002; name_ = bs; break; } case 24: { int rawValue = input.readEnum(); @SuppressWarnings("deprecation") com.squareup.PersonOuterClass.Person.Occupation value = com.squareup.PersonOuterClass.Person.Occupation.valueOf(rawValue); if (value == null) { unknownFields.mergeVarintField(3, rawValue); } else { bitField0_ |= 0x00000004; occupation_ = rawValue;
  48. public Occupation findValueByNumber(int number) { return Occupation.forNumber(number); } }; public

    final com.google.protobuf.Descriptors.EnumValueDescriptor getValueDescriptor() { return getDescriptor().getValues().get(ordinal()); } public final com.google.protobuf.Descriptors.EnumDescriptor getDescriptorForType() { return getDescriptor(); } public static final com.google.protobuf.Descriptors.EnumDescriptor getDescriptor() { return com.squareup.PersonOuterClass.Person.getDescriptor().getEnumTypes().get(0); } private static final Occupation[] VALUES = values(); public static Occupation valueOf( com.google.protobuf.Descriptors.EnumValueDescriptor desc) { if (desc.getType() != getDescriptor()) { throw new java.lang.IllegalArgumentException( "EnumValueDescriptor is not for this type."); } return VALUES[desc.getIndex()];
  49. */ public boolean hasOccupation() { return ((bitField0_ & 0x00000004) !=

    0); } /** * <code>optional .com.squareup.Person.Occupation occupation = 3;</code> */ public com.squareup.PersonOuterClass.Person.Occupation getOccupation() { @SuppressWarnings("deprecation") com.squareup.PersonOuterClass.Person.Occupation result = com.squareup.PersonOuterClass.Person.Occupation.valueOf(occupation_); return result == null ? com.squareup.PersonOuterClass.Person.Occupation.SOFTWARE_DEVELOPER : result; } private byte memoizedIsInitialized = -1; @java.lang.Override public final boolean isInitialized() { byte isInitialized = memoizedIsInitialized; if (isInitialized == 1) return true; if (isInitialized == 0) return false; if (!hasId()) { memoizedIsInitialized = 0; return false; } memoizedIsInitialized = 1;
  50. } if (!(obj instanceof com.squareup.PersonOuterClass.Person)) { return super.equals(obj); } com.squareup.PersonOuterClass.Person

    other = (com.squareup.PersonOuterClass.Person) obj if (hasId() != other.hasId()) return false; if (hasId()) { if (getId() != other.getId()) return false; } if (hasName() != other.hasName()) return false; if (hasName()) { if (!getName() .equals(other.getName())) return false; } if (hasOccupation() != other.hasOccupation()) return false; if (hasOccupation()) {
  51. None
  52. Not readable for both Egor or Benoit ~95 methods for

    3 fields + 3 values enum
  53. None
  54. Small number of methods Readable code Immutable Support for chained

    builder pattern Inherit documentation from .proto files
  55. WIRE 3

  56. gRPC support Kotlin code generation Gradle plugin

  57. gRPC support

  58. What RPC are you ?

  59. service RouteGuide {a }b

  60. service RouteGuide {a // A simple RPC. rpc GetFeature(Point) returns

    (Feature) {} }b
  61. service RouteGuide {a // A simple RPC. rpc GetFeature(Point) returns

    (Feature) {} // A server-to-client streaming RPC. rpc ListFeatures(Rectangle) returns (stream Feature) {} }b
  62. service RouteGuide {a // A simple RPC. rpc GetFeature(Point) returns

    (Feature) {} // A server-to-client streaming RPC. rpc ListFeatures(Rectangle) returns (stream Feature) {} // A client-to-server streaming RPC. rpc RecordRoute(stream Point) returns (RouteSummary) {} }b
  63. service RouteGuide {a // A simple RPC. rpc GetFeature(Point) returns

    (Feature) {} // A server-to-client streaming RPC. rpc ListFeatures(Rectangle) returns (stream Feature) {} // A client-to-server streaming RPC. rpc RecordRoute(stream Point) returns (RouteSummary) {} // A Bidirectional streaming RPC. rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} }b
  64. service RouteGuide {a // A simple RPC. rpc GetFeature(Point) returns

    (Feature) {} // A server-to-client streaming RPC. rpc ListFeatures(Rectangle) returns (stream Feature) {} // A client-to-server streaming RPC. rpc RecordRoute(stream Point) returns (RouteSummary) {} // A Bidirectional streaming RPC. rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} }b
  65. Which side are you on ?

  66. rpcRole = 'client or server?'

  67. rpcRole = 'client or server?' enum class RpcRole { CLIENT,

    SERVER; }
  68. rpcCallStyle = 'suspending or blocking?'

  69. rpcCallStyle = 'suspending or blocking?' enum class RpcCallStyle { SUSPENDING,

    BLOCKING; }
  70. singleMethodServices = true || false

  71. rpcCallStyle = 'blocking' rpcRole = 'server' singleMethodServices = true rpcCallStyle

    = 'suspending' rpcRole = 'client' singleMethodServices = false Client ? Server ?
  72. gRPC codegen

  73. Client Side service RouteGuide {a rpc GetFeature(Point) returns (Feature) {}

    rpc ListFeatures(Rectangle) returns (stream Feature) {} rpc RecordRoute(stream Point) returns (RouteSummary) {} rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} }b wire { kotlin { rpcCallStyle = 'suspending' rpcRole = 'client' singleMethodServices = false } }
  74. interface RouteGuideClient : Service { } Client Side service RouteGuide

    {a rpc GetFeature(Point) returns (Feature) {} rpc ListFeatures(Rectangle) returns (stream Feature) {} rpc RecordRoute(stream Point) returns (RouteSummary) {} rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} }b
  75. interface RouteGuideClient : Service { suspend fun GetFeature(request: Point): Feature

    } Client Side service RouteGuide {a rpc GetFeature(Point) returns (Feature) {} rpc ListFeatures(Rectangle) returns (stream Feature) {} rpc RecordRoute(stream Point) returns (RouteSummary) {} rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} }b
  76. interface RouteGuideClient : Service { suspend fun GetFeature(request: Point): Feature

    fun ListFeatures(request: Rectangle): ReceiveChannel<Feature> } Client Side service RouteGuide {a rpc GetFeature(Point) returns (Feature) {} rpc ListFeatures(Rectangle) returns (stream Feature) {} rpc RecordRoute(stream Point) returns (RouteSummary) {} rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} }b
  77. interface RouteGuideClient : Service { suspend fun GetFeature(request: Point): Feature

    fun ListFeatures(request: Rectangle): ReceiveChannel<Feature> fun RecordRoute(): Pair<SendChannel<Point>, Deferred<RouteSummary>> } Client Side service RouteGuide {a rpc GetFeature(Point) returns (Feature) {} rpc ListFeatures(Rectangle) returns (stream Feature) {} rpc RecordRoute(stream Point) returns (RouteSummary) {} rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} }b
  78. interface RouteGuideClient : Service { suspend fun GetFeature(request: Point): Feature

    fun ListFeatures(request: Rectangle): ReceiveChannel<Feature> fun RecordRoute(): Pair<SendChannel<Point>, Deferred<RouteSummary>> fun RouteChat(): Pair<SendChannel<RouteNote>, ReceiveChannel<RouteNote>> } Client Side service RouteGuide {a rpc GetFeature(Point) returns (Feature) {} rpc ListFeatures(Rectangle) returns (stream Feature) {} rpc RecordRoute(stream Point) returns (RouteSummary) {} rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} }b
  79. interface RouteGuideClient : Service { suspend fun GetFeature(request: Point): Feature

    fun ListFeatures(request: Rectangle): ReceiveChannel<Feature> fun RecordRoute(): Pair<SendChannel<Point>, Deferred<RouteSummary>> fun RouteChat(): Pair<SendChannel<RouteNote>, ReceiveChannel<RouteNote>> } Client Side service RouteGuide {a rpc GetFeature(Point) returns (Feature) {} rpc ListFeatures(Rectangle) returns (stream Feature) {} rpc RecordRoute(stream Point) returns (RouteSummary) {} rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} }b
  80. interface RouteGuideClient : Service { suspend fun GetFeature(request: Point): Feature

    fun ListFeatures(request: Rectangle): ReceiveChannel<Feature> fun RecordRoute(): Pair<SendChannel<Point>, Deferred<RouteSummary>> fun RouteChat(): Pair<SendChannel<RouteNote>, ReceiveChannel<RouteNote>> } Client Side service RouteGuide {a rpc GetFeature(Point) returns (Feature) {} rpc ListFeatures(Rectangle) returns (stream Feature) {} rpc RecordRoute(stream Point) returns (RouteSummary) {} rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} }b val grpcClient = GrpcClient.Builder() .client(okHttpClient) .baseUrl("https://10.0.2.2:8443") .build() val whiteboardClient = grpcClient.create(WhiteboardClient::class)
  81. interface RouteGuideClient : Service { suspend fun GetFeature(request: Point): Feature

    fun ListFeatures(request: Rectangle): ReceiveChannel<Feature> fun RecordRoute(): Pair<SendChannel<Point>, Deferred<RouteSummary>> fun RouteChat(): Pair<SendChannel<RouteNote>, ReceiveChannel<RouteNote>> } Client Side service RouteGuide {a rpc GetFeature(Point) returns (Feature) {} rpc ListFeatures(Rectangle) returns (stream Feature) {} rpc RecordRoute(stream Point) returns (RouteSummary) {} rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} }b
  82. interface RouteGuideClient : Service { suspend fun GetFeature(request: Point): Feature

    fun ListFeatures(request: Rectangle): ReceiveChannel<Feature> fun RecordRoute(): Pair<SendChannel<Point>, Deferred<RouteSummary>> fun RouteChat(): Pair<SendChannel<RouteNote>, ReceiveChannel<RouteNote>> } Client Side dependencies { implementation 'com.squareup.wire:wire-grpc-client:3.0.0-rc01' }
  83. Server Side service RouteGuide {a rpc GetFeature(Point) returns (Feature) {}

    rpc ListFeatures(Rectangle) returns (stream Feature) {} rpc RecordRoute(stream Point) returns (RouteSummary) {} rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} }b wire { kotlin { rpcCallStyle = 'blocking' rpcRole = 'server' singleMethodServices = true } }
  84. interface RouteGuideBlockingServer : Service { } Server Side service RouteGuide

    {a rpc GetFeature(Point) returns (Feature) {} rpc ListFeatures(Rectangle) returns (stream Feature) {} rpc RecordRoute(stream Point) returns (RouteSummary) {} rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} }b
  85. interface RouteGuideBlockingServer : Service { fun GetFeature(request: Point): Feature }

    Server Side service RouteGuide {a rpc GetFeature(Point) returns (Feature) {} rpc ListFeatures(Rectangle) returns (stream Feature) {} rpc RecordRoute(stream Point) returns (RouteSummary) {} rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} }b
  86. interface RouteGuideBlockingServer : Service { fun GetFeature(request: Point): Feature fun

    ListFeatures(request: Rectangle, response: MessageSink<Feature>) } Server Side service RouteGuide {a rpc GetFeature(Point) returns (Feature) {} rpc ListFeatures(Rectangle) returns (stream Feature) {} rpc RecordRoute(stream Point) returns (RouteSummary) {} rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} }b
  87. interface RouteGuideBlockingServer : Service { fun GetFeature(request: Point): Feature fun

    ListFeatures(request: Rectangle, response: MessageSink<Feature>) } Server Side service RouteGuide {a rpc GetFeature(Point) returns (Feature) {} rpc ListFeatures(Rectangle) returns (stream Feature) {} rpc RecordRoute(stream Point) returns (RouteSummary) {} rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} }b interface MessageSink<in T : Any> { fun write(message: T) fun close() }
  88. interface RouteGuideBlockingServer : Service { fun GetFeature(request: Point): Feature fun

    ListFeatures(request: Rectangle, response: MessageSink<Feature>) } Server Side service RouteGuide {a rpc GetFeature(Point) returns (Feature) {} rpc ListFeatures(Rectangle) returns (stream Feature) {} rpc RecordRoute(stream Point) returns (RouteSummary) {} rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} }b
  89. interface RouteGuideBlockingServer : Service { fun GetFeature(request: Point): Feature fun

    ListFeatures(request: Rectangle, response: MessageSink<Feature>) fun RecordRoute(request: MessageSource<Point>): RouteSummary } Server Side service RouteGuide {a rpc GetFeature(Point) returns (Feature) {} rpc ListFeatures(Rectangle) returns (stream Feature) {} rpc RecordRoute(stream Point) returns (RouteSummary) {} rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} }b
  90. interface RouteGuideBlockingServer : Service { fun GetFeature(request: Point): Feature fun

    ListFeatures(request: Rectangle, response: MessageSink<Feature>) fun RecordRoute(request: MessageSource<Point>): RouteSummary } Server Side service RouteGuide {a rpc GetFeature(Point) returns (Feature) {} rpc ListFeatures(Rectangle) returns (stream Feature) {} rpc RecordRoute(stream Point) returns (RouteSummary) {} rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} }b interface MessageSource<out T : Any> { fun read(): T? fun close() }
  91. interface RouteGuideBlockingServer : Service { fun GetFeature(request: Point): Feature fun

    ListFeatures(request: Rectangle, response: MessageSink<Feature>) fun RecordRoute(request: MessageSource<Point>): RouteSummary } Server Side service RouteGuide {a rpc GetFeature(Point) returns (Feature) {} rpc ListFeatures(Rectangle) returns (stream Feature) {} rpc RecordRoute(stream Point) returns (RouteSummary) {} rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} }b
  92. interface RouteGuideBlockingServer : Service { fun GetFeature(request: Point): Feature fun

    ListFeatures(request: Rectangle, response: MessageSink<Feature>) fun RecordRoute(request: MessageSource<Point>): RouteSummary fun RouteChat(request: MessageSource<RouteNote>, response: MessageSink<RouteNote>) } Server Side service RouteGuide {a rpc GetFeature(Point) returns (Feature) {} rpc ListFeatures(Rectangle) returns (stream Feature) {} rpc RecordRoute(stream Point) returns (RouteSummary) {} rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} }b
  93. interface RouteGuideBlockingServer : Service { fun GetFeature(request: Point): Feature fun

    ListFeatures(request: Rectangle, response: MessageSink<Feature>) fun RecordRoute(request: MessageSource<Point>): RouteSummary fun RouteChat(request: MessageSource<RouteNote>, response: MessageSink<RouteNote>) } Server Side service RouteGuide {a rpc GetFeature(Point) returns (Feature) {} rpc ListFeatures(Rectangle) returns (stream Feature) {} rpc RecordRoute(stream Point) returns (RouteSummary) {} rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} }b
  94. interface RouteGuideBlockingServer : Service { fun GetFeature(request: Point): Feature fun

    ListFeatures(request: Rectangle, response: MessageSink<Feature>) fun RecordRoute(request: MessageSource<Point>): RouteSummary fun RouteChat(request: MessageSource<RouteNote>, response: MessageSink<RouteNote>) } Server Side dependencies { api 'com.squareup.wire:wire-runtime:3.0.0-rc01' }
  95. Kotlin coroutine or not Kotlin coroutine ?

  96. Kotlin Codegen

  97. class Person( @field:WireField( tag = 1, adapter = "com.squareup.wire.ProtoAdapter#INT32", label

    = WireField.Label.REQUIRED ) val id: Int, @field:WireField( tag = 2, adapter = "com.squareup.wire.ProtoAdapter#STRING" ) val name: String? = null, @field:WireField( tag = 3, adapter = "com.squareup.Person${'$'}Occupation#ADAPTER" ) val occupation: Occupation? = null, unknownFields: ByteString = ByteString.EMPTY ) : Message<Person, Nothing>(ADAPTER, unknownFields) { override fun equals(other: Any?): Boolean { if (other === this) return true if (other !is Person) return false return unknownFields == other.unknownFields && id == other.id
  98. Data classes, but not quite!

  99. class Person( @field:WireField( tag = 1, adapter = "com.squareup.wire.ProtoAdapter#INT32", label

    = WireField.Label.REQUIRED ) val id: Int, @field:WireField( tag = 2, adapter = "com.squareup.wire.ProtoAdapter#STRING" ) val name: String? = null, @field:WireField( tag = 3, adapter = "com.squareup.Person${'$'}Occupation#ADAPTER" ) val occupation: Occupation? = null, unknownFields: ByteString = ByteString.EMPTY ) : Message<Person, Nothing>(ADAPTER, unknownFields) { override fun equals(other: Any?): Boolean { if (other === this) return true if (other !is Person) return false return unknownFields == other.unknownFields && id == other.id
  100. val occupation: Occupation? = null, unknownFields: ByteString = ByteString.EMPTY )

    : Message<Person, Nothing>(ADAPTER, unknownFields) { override fun equals(other: Any?): Boolean { if (other === this) return true if (other !is Person) return false return unknownFields == other.unknownFields && id == other.id && name == other.name && occupation == other.occupation } override fun hashCode(): Int { var result = super.hashCode if (result == 0) { result = id.hashCode() result = result * 37 + name.hashCode() result = result * 37 + occupation.hashCode() super.hashCode = result } return result } override fun toString(): String { val result = mutableListOf<String>() result += """id=$id""" if (name != null) result += """name=$name"""
  101. && name == other.name && occupation == other.occupation } override

    fun hashCode(): Int { var result = super.hashCode if (result == 0) { result = id.hashCode() result = result * 37 + name.hashCode() result = result * 37 + occupation.hashCode() super.hashCode = result } return result } override fun toString(): String { val result = mutableListOf<String>() result += """id=$id""" if (name != null) result += """name=$name""" if (occupation != null) result += """occupation=$occupation""" return result.joinToString(prefix = "Person{", separator = ", ", postfix = "}") } @Deprecated( message = "Shouldn't be used in Kotlin", level = DeprecationLevel.HIDDEN ) override fun newBuilder(): Nothing { throw AssertionError()
  102. } return result } override fun toString(): String { val

    result = mutableListOf<String>() result += """id=$id""" if (name != null) result += """name=$name""" if (occupation != null) result += """occupation=$occupation""" return result.joinToString(prefix = "Person{", separator = ", ", postfix = "}") } @Deprecated( message = "Shouldn't be used in Kotlin", level = DeprecationLevel.HIDDEN ) override fun newBuilder(): Nothing { throw AssertionError() } fun copy( id: Int = this.id, name: String? = this.name, occupation: Occupation? = this.occupation, unknownFields: ByteString = this.unknownFields ): Person = Person(id, name, occupation, unknownFields) companion object { @JvmField
  103. Data classes, but not quite! Builder deprecated, use copy()

  104. class Person( @field:WireField( tag = 1, adapter = "com.squareup.wire.ProtoAdapter#INT32", label

    = WireField.Label.REQUIRED ) val id: Int, @field:WireField( tag = 2, adapter = "com.squareup.wire.ProtoAdapter#STRING" ) val name: String? = null, @field:WireField( tag = 3, adapter = "com.squareup.Person${'$'}Occupation#ADAPTER" ) val occupation: Occupation? = null, unknownFields: ByteString = ByteString.EMPTY ) : Message<Person, Nothing>(ADAPTER, unknownFields) { override fun equals(other: Any?): Boolean { if (other === this) return true if (other !is Person) return false return unknownFields == other.unknownFields && id == other.id
  105. if (occupation != null) result += """occupation=$occupation""" return result.joinToString(prefix =

    "Person{", separator = ", ", postfix = "}") } @Deprecated( message = "Shouldn't be used in Kotlin", level = DeprecationLevel.HIDDEN ) override fun newBuilder(): Nothing { throw AssertionError() } fun copy( id: Int = this.id, name: String? = this.name, occupation: Occupation? = this.occupation, unknownFields: ByteString = this.unknownFields ): Person = Person(id, name, occupation, unknownFields) companion object { @JvmField val ADAPTER: ProtoAdapter<Person> = object : ProtoAdapter<Person>( FieldEncoding.LENGTH_DELIMITED, Person::class ) { override fun encodedSize(value: Person): Int = ProtoAdapter.INT32.encodedSizeWithTag(1, value.id) + ProtoAdapter.STRING.encodedSizeWithTag(2, value.name) + Occupation.ADAPTER.encodedSizeWithTag(3, value.occupation) +
  106. Data classes, but not quite! Builder deprecated, use copy() Nullability:

    required vs optional
  107. class Person( @field:WireField( tag = 1, adapter = "com.squareup.wire.ProtoAdapter#INT32", label

    = WireField.Label.REQUIRED ) val id: Int, @field:WireField( tag = 2, adapter = "com.squareup.wire.ProtoAdapter#STRING" ) val name: String? = null, @field:WireField( tag = 3, adapter = "com.squareup.Person${'$'}Occupation#ADAPTER" ) val occupation: Occupation? = null, unknownFields: ByteString = ByteString.EMPTY ) : Message<Person, Nothing>(ADAPTER, unknownFields) { override fun equals(other: Any?): Boolean { if (other === this) return true if (other !is Person) return false return unknownFields == other.unknownFields && id == other.id
  108. Data classes, but not quite! Builder deprecated, use copy() Nullability:

    required vs optional Interoperability with Java
  109. class Person( @field:WireField( tag = 1, adapter = "com.squareup.wire.ProtoAdapter#INT32", label

    = WireField.Label.REQUIRED ) @JvmField val id: Int, @field:WireField( tag = 2, adapter = "com.squareup.wire.ProtoAdapter#STRING" ) @JvmField val name: String? = null, @field:WireField( tag = 3, adapter = "com.squareup.Person${'$'}Occupation#ADAPTER" ) @JvmField val occupation: Occupation? = null, unknownFields: ByteString = ByteString.EMPTY ) : Message<Person, Person.Builder>(ADAPTER, unknownFields) { override fun equals(other: Any?): Boolean {
  110. if (occupation != null) result += """occupation=$occupation""" return result.joinToString(prefix =

    "Person{", separator = ", ", postfix = "}") } override fun newBuilder(): Builder { val builder = Builder() builder.id = id builder.name = name builder.occupation = occupation builder.addUnknownFields(unknownFields) return builder } fun copy( id: Int = this.id, name: String? = this.name, occupation: Occupation? = this.occupation, unknownFields: ByteString = this.unknownFields ): Person = Person(id, name, occupation, unknownFields) class Builder : Message.Builder<Person, Builder>() { @JvmField var id: Int? = null @JvmField var name: String? = null @JvmField
  111. occupation: Occupation? = this.occupation, unknownFields: ByteString = this.unknownFields ): Person

    = Person(id, name, occupation, unknownFields) class Builder : Message.Builder<Person, Builder>() { @JvmField var id: Int? = null @JvmField var name: String? = null @JvmField var occupation: Occupation? = null fun id(id: Int): Builder { this.id = id return this } fun name(name: String?): Builder { this.name = name return this } fun occupation(occupation: Occupation?): Builder { this.occupation = occupation return this }
  112. Bonus: multiplatform runtime!* * - coming soon

  113. // wire-runtime apply plugin: 'org.jetbrains.kotlin.multiplatform' kotlin { jvm { withJava()

    } js() iosX64() iosArm64() linuxX64() macosX64() }
  114. None
  115. Gradle Plugin

  116. tasks.register("generateProtos", JavaExec) { classpath = configurations.wire main = 'com.squareup.wire.WireCompiler' args

    = [ "--proto_path=$rootDir/protos/src/main/proto", '--kotlin_out=src/main/java', '--includes=com.squareup.wire.whiteboard.Whiteboard', ] } Wire < 3.0
  117. apply plugin: 'com.squareup.wire' wire { sourcePath { srcDir "$rootDir/protos/src/main/proto" }

    kotlin { out "src/main/java" includes = ['com.squareup.wire.whiteboard.Whiteboard'] } } WIRE 3
  118. wire { java { includes = ['com.example.pizza.*'] excludes = ['com.example.sales.*']

    exclusive = false out "${buildDir}/custom" android = true androidAnnotations = true compact = true } }
  119. wire { kotlin { includes = ['com.example.pizza.*'] excludes = ['com.example.sales.*']

    exclusive = false out "${buildDir}/custom" android = true javaInterop = true rpcCallStyle = 'blocking' rpcRole = 'server' singleMethodServices = true } }
  120. WIRE 3 Real World Business Use Case Demo

  121. Demo Video: https://youtu.be/hGs5wSS7CqA Source Code: https://github.com/square/wire/tree/master/wire-grpc-sample

  122. What's next ?

  123. Improve multiplatform support Stabilize gRPC APIs gRPC for Java? Gradle

    plugin polishing for Android
  124. WIRE III

  125. References • gRPC official website: https://grpc.io • Protocol Buffers documentations:

    https://developers.google.com/protocol-buffers/ • Wire documentations: https://square.github.io/wire/ • Wire source code repository: https://github.com/square/wire/ • Misk microservice container: https://github.com/cashapp/misk