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

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.

Benoît Quenaudon

August 27, 2019
Tweet

More Decks by Benoît Quenaudon

Other Decks in Programming

Transcript

  1. 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; } }
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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
  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; } } val person = Person( id = 23, name = "egor", occupation = PRESIDENT ) val bytes = Person.ADAPTER.encode(person) println(bytes.toByteString().hex()) // 0817120465676f721802
  11. "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
  12. 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
  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; } } 08 17 12 04 65 67 6f 72
  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; } } 08 17 12 Tag: id(1) Field encoding: VARINT(0) 0000 0001 << 3 0000 1000 | 0 0000 1000 == 08
  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; } } 08 17 12 04 65 67 6f 72 18 id = 23
  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; } } 08 17 12 04 Tag: name(2) Field encoding: LENGTH_DELIMITED(2) 0000 0010 << 3 0001 0000 | 2 0000 1010 == 12
  17. 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()
  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; } } 17 12 04 65 67 6f 72 18 02 “egor”.length() ‘e’ ‘g’ ‘o’ ‘r’
  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; } } 6f 72 18 02 Tag: occupation(3) Field encoding: VARINT(0) 0000 0011 << 3 0001 1000 | 0 0001 1000 == 18
  20. 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
  21. message Person { required int32 id = 1; optional string

    name = 2; optional Occupation occupation = 3; enum Occupation { SOFTWARE_DEVELOPER = 0; STUDENT = 1; PRESIDENT = 2; } } v1
  22. 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
  23. 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
  24. 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}
  25. 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}
  26. 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}
  27. 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}
  28. 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
  29. 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}
  30. 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}
  31. message Person { required int32 id = 1; optional string

    name = 2; optional Occupation occupation = 3; enum Occupation { SOFTWARE_DEVELOPER = 0; STUDENT = 1; PRESIDENT = 2; } }
  32. // 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> */
  33. */ 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(); }
  34. 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;
  35. 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()];
  36. */ 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;
  37. } 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()) {
  38. Small number of methods Readable code Immutable Support for chained

    builder pattern Inherit documentation from .proto files
  39. service RouteGuide {a // A simple RPC. rpc GetFeature(Point) returns

    (Feature) {} // A server-to-client streaming RPC. rpc ListFeatures(Rectangle) returns (stream Feature) {} }b
  40. 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
  41. 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
  42. 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
  43. rpcCallStyle = 'blocking' rpcRole = 'server' singleMethodServices = true rpcCallStyle

    = 'suspending' rpcRole = 'client' singleMethodServices = false Client ? Server ?
  44. 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 } }
  45. 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
  46. 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
  47. 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
  48. 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
  49. 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
  50. 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
  51. 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)
  52. 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
  53. 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' }
  54. 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 } }
  55. 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
  56. 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
  57. 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
  58. 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() }
  59. 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
  60. 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
  61. 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() }
  62. 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
  63. 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
  64. 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
  65. 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' }
  66. 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
  67. 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
  68. 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"""
  69. && 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()
  70. } 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
  71. 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
  72. 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) +
  73. 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
  74. Data classes, but not quite! Builder deprecated, use copy() Nullability:

    required vs optional Interoperability with Java
  75. 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 {
  76. 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
  77. 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 }
  78. 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
  79. 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
  80. wire { java { includes = ['com.example.pizza.*'] excludes = ['com.example.sales.*']

    exclusive = false out "${buildDir}/custom" android = true androidAnnotations = true compact = true } }
  81. 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 } }
  82. 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