Slide 1

Slide 1 text

Tackling gRPC with Kotlin WIRE 3 @oldergod @egorand

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

Why gRPC?

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

Protocol Buffers

Slide 7

Slide 7 text

Schema-based Rich tooling support Lightweight on the wire Strong backwards compatibility guarantees

Slide 8

Slide 8 text

Schema

Slide 9

Slide 9 text

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; } }

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

Bytes

Slide 18

Slide 18 text

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; } }

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

"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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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()

Slide 27

Slide 27 text

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’

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

Backwards compatibility

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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}

Slide 35

Slide 35 text

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}

Slide 36

Slide 36 text

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}

Slide 37

Slide 37 text

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}

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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}

Slide 40

Slide 40 text

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}

Slide 41

Slide 41 text

Supported languages Java C# C++ Python Objective-C JavaScript Ruby Go Dart PHP

Slide 42

Slide 42 text

Why WIRE ?

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

No content

Slide 45

Slide 45 text

// 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 { /** * required int32 id = 1; */ boolean hasId(); /** * required int32 id = 1; */

Slide 46

Slide 46 text

*/ 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(); }

Slide 47

Slide 47 text

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;

Slide 48

Slide 48 text

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()];

Slide 49

Slide 49 text

*/ public boolean hasOccupation() { return ((bitField0_ & 0x00000004) != 0); } /** * optional .com.squareup.Person.Occupation occupation = 3; */ 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;

Slide 50

Slide 50 text

} 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()) {

Slide 51

Slide 51 text

No content

Slide 52

Slide 52 text

Not readable for both Egor or Benoit ~95 methods for 3 fields + 3 values enum

Slide 53

Slide 53 text

No content

Slide 54

Slide 54 text

Small number of methods Readable code Immutable Support for chained builder pattern Inherit documentation from .proto files

Slide 55

Slide 55 text

WIRE 3

Slide 56

Slide 56 text

gRPC support Kotlin code generation Gradle plugin

Slide 57

Slide 57 text

gRPC support

Slide 58

Slide 58 text

What RPC are you ?

Slide 59

Slide 59 text

service RouteGuide {a }b

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

Which side are you on ?

Slide 66

Slide 66 text

rpcRole = 'client or server?'

Slide 67

Slide 67 text

rpcRole = 'client or server?' enum class RpcRole { CLIENT, SERVER; }

Slide 68

Slide 68 text

rpcCallStyle = 'suspending or blocking?'

Slide 69

Slide 69 text

rpcCallStyle = 'suspending or blocking?' enum class RpcCallStyle { SUSPENDING, BLOCKING; }

Slide 70

Slide 70 text

singleMethodServices = true || false

Slide 71

Slide 71 text

rpcCallStyle = 'blocking' rpcRole = 'server' singleMethodServices = true rpcCallStyle = 'suspending' rpcRole = 'client' singleMethodServices = false Client ? Server ?

Slide 72

Slide 72 text

gRPC codegen

Slide 73

Slide 73 text

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 } }

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

interface RouteGuideClient : Service { suspend fun GetFeature(request: Point): Feature fun ListFeatures(request: Rectangle): ReceiveChannel } 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

Slide 77

Slide 77 text

interface RouteGuideClient : Service { suspend fun GetFeature(request: Point): Feature fun ListFeatures(request: Rectangle): ReceiveChannel fun RecordRoute(): Pair, Deferred> } 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

Slide 78

Slide 78 text

interface RouteGuideClient : Service { suspend fun GetFeature(request: Point): Feature fun ListFeatures(request: Rectangle): ReceiveChannel fun RecordRoute(): Pair, Deferred> fun RouteChat(): Pair, ReceiveChannel> } 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

Slide 79

Slide 79 text

interface RouteGuideClient : Service { suspend fun GetFeature(request: Point): Feature fun ListFeatures(request: Rectangle): ReceiveChannel fun RecordRoute(): Pair, Deferred> fun RouteChat(): Pair, ReceiveChannel> } 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

Slide 80

Slide 80 text

interface RouteGuideClient : Service { suspend fun GetFeature(request: Point): Feature fun ListFeatures(request: Rectangle): ReceiveChannel fun RecordRoute(): Pair, Deferred> fun RouteChat(): Pair, ReceiveChannel> } 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)

Slide 81

Slide 81 text

interface RouteGuideClient : Service { suspend fun GetFeature(request: Point): Feature fun ListFeatures(request: Rectangle): ReceiveChannel fun RecordRoute(): Pair, Deferred> fun RouteChat(): Pair, ReceiveChannel> } 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

Slide 82

Slide 82 text

interface RouteGuideClient : Service { suspend fun GetFeature(request: Point): Feature fun ListFeatures(request: Rectangle): ReceiveChannel fun RecordRoute(): Pair, Deferred> fun RouteChat(): Pair, ReceiveChannel> } Client Side dependencies { implementation 'com.squareup.wire:wire-grpc-client:3.0.0-rc01' }

Slide 83

Slide 83 text

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 } }

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

interface RouteGuideBlockingServer : Service { fun GetFeature(request: Point): Feature fun ListFeatures(request: Rectangle, response: MessageSink) } 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

Slide 87

Slide 87 text

interface RouteGuideBlockingServer : Service { fun GetFeature(request: Point): Feature fun ListFeatures(request: Rectangle, response: MessageSink) } 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 { fun write(message: T) fun close() }

Slide 88

Slide 88 text

interface RouteGuideBlockingServer : Service { fun GetFeature(request: Point): Feature fun ListFeatures(request: Rectangle, response: MessageSink) } 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

Slide 89

Slide 89 text

interface RouteGuideBlockingServer : Service { fun GetFeature(request: Point): Feature fun ListFeatures(request: Rectangle, response: MessageSink) fun RecordRoute(request: MessageSource): 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

Slide 90

Slide 90 text

interface RouteGuideBlockingServer : Service { fun GetFeature(request: Point): Feature fun ListFeatures(request: Rectangle, response: MessageSink) fun RecordRoute(request: MessageSource): 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 { fun read(): T? fun close() }

Slide 91

Slide 91 text

interface RouteGuideBlockingServer : Service { fun GetFeature(request: Point): Feature fun ListFeatures(request: Rectangle, response: MessageSink) fun RecordRoute(request: MessageSource): 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

Slide 92

Slide 92 text

interface RouteGuideBlockingServer : Service { fun GetFeature(request: Point): Feature fun ListFeatures(request: Rectangle, response: MessageSink) fun RecordRoute(request: MessageSource): RouteSummary fun RouteChat(request: MessageSource, response: MessageSink) } 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

Slide 93

Slide 93 text

interface RouteGuideBlockingServer : Service { fun GetFeature(request: Point): Feature fun ListFeatures(request: Rectangle, response: MessageSink) fun RecordRoute(request: MessageSource): RouteSummary fun RouteChat(request: MessageSource, response: MessageSink) } 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

Slide 94

Slide 94 text

interface RouteGuideBlockingServer : Service { fun GetFeature(request: Point): Feature fun ListFeatures(request: Rectangle, response: MessageSink) fun RecordRoute(request: MessageSource): RouteSummary fun RouteChat(request: MessageSource, response: MessageSink) } Server Side dependencies { api 'com.squareup.wire:wire-runtime:3.0.0-rc01' }

Slide 95

Slide 95 text

Kotlin coroutine or not Kotlin coroutine ?

Slide 96

Slide 96 text

Kotlin Codegen

Slide 97

Slide 97 text

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(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

Slide 98

Slide 98 text

Data classes, but not quite!

Slide 99

Slide 99 text

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(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

Slide 100

Slide 100 text

val occupation: Occupation? = null, unknownFields: ByteString = ByteString.EMPTY ) : Message(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() result += """id=$id""" if (name != null) result += """name=$name"""

Slide 101

Slide 101 text

&& 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() 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()

Slide 102

Slide 102 text

} return result } override fun toString(): String { val result = mutableListOf() 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

Slide 103

Slide 103 text

Data classes, but not quite! Builder deprecated, use copy()

Slide 104

Slide 104 text

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(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

Slide 105

Slide 105 text

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 = object : ProtoAdapter( 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) +

Slide 106

Slide 106 text

Data classes, but not quite! Builder deprecated, use copy() Nullability: required vs optional

Slide 107

Slide 107 text

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(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

Slide 108

Slide 108 text

Data classes, but not quite! Builder deprecated, use copy() Nullability: required vs optional Interoperability with Java

Slide 109

Slide 109 text

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(ADAPTER, unknownFields) { override fun equals(other: Any?): Boolean {

Slide 110

Slide 110 text

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() { @JvmField var id: Int? = null @JvmField var name: String? = null @JvmField

Slide 111

Slide 111 text

occupation: Occupation? = this.occupation, unknownFields: ByteString = this.unknownFields ): Person = Person(id, name, occupation, unknownFields) class Builder : Message.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 }

Slide 112

Slide 112 text

Bonus: multiplatform runtime!* * - coming soon

Slide 113

Slide 113 text

// wire-runtime apply plugin: 'org.jetbrains.kotlin.multiplatform' kotlin { jvm { withJava() } js() iosX64() iosArm64() linuxX64() macosX64() }

Slide 114

Slide 114 text

No content

Slide 115

Slide 115 text

Gradle Plugin

Slide 116

Slide 116 text

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

Slide 117

Slide 117 text

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

Slide 118

Slide 118 text

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

Slide 119

Slide 119 text

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 } }

Slide 120

Slide 120 text

WIRE 3 Real World Business Use Case Demo

Slide 121

Slide 121 text

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

Slide 122

Slide 122 text

What's next ?

Slide 123

Slide 123 text

Improve multiplatform support Stabilize gRPC APIs gRPC for Java? Gradle plugin polishing for Android

Slide 124

Slide 124 text

WIRE III

Slide 125

Slide 125 text

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