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. Tackling gRPC with Kotlin
    WIRE 3
    @oldergod @egorand

    View full-size slide

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

    View full-size slide

  3. Protocol Buffers

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  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;
    }
    }
    Syntax
    proto2 proto3
    Package
    Options
    java_package custom
    Messages
    Enums
    Tags

    View full-size slide

  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;
    }
    }
    Syntax
    proto2 proto3
    Package
    Options
    java_package custom
    Messages
    Enums
    Tags
    int 1 to 536,870,911
    Field rules

    View full-size slide

  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;
    }
    }
    Package
    Options
    java_package custom
    Messages
    Enums
    Tags
    int 1 to 536,870,911
    Field rules
    required optional repeated

    View full-size slide

  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;
    }
    }
    java_package custom
    Messages
    Enums
    Tags
    int 1 to 536,870,911
    Field rules
    required optional repeated
    Field types
    double float int32 int64 uint32

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  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;
    }
    }
    Field rules
    required optional repeated
    Field types
    double float int32 int64 uint32
    uint64 sint32 sint64 fixed32 fixed64
    sfixed32 sfixed64 bool string
    bytes

    View full-size slide

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

    View full-size slide

  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;
    }
    }
    val person = Person(
    id = 23,
    name = "egor",
    occupation = PRESIDENT
    )
    val bytes = Person.ADAPTER.encode(person)
    println(bytes.toByteString().hex())
    // 0817120465676f721802

    View full-size slide

  15. "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

    View full-size slide

  16. 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

    View full-size slide

  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;
    }
    }
    08
    17
    12
    04
    65
    67
    6f
    72

    View full-size slide

  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;
    }
    }
    08
    17
    12
    Tag: id(1)
    Field encoding: VARINT(0)
    0000 0001
    << 3
    0000 1000
    | 0
    0000 1000
    ==
    08

    View full-size slide

  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;
    }
    }
    08
    17
    12
    04
    65
    67
    6f
    72
    18
    id = 23

    View full-size slide

  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;
    }
    }
    08
    17
    12
    04
    Tag: name(2)
    Field encoding:
    LENGTH_DELIMITED(2)
    0000 0010
    << 3
    0001 0000
    | 2
    0000 1010
    ==
    12

    View full-size slide

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

    View full-size slide

  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;
    }
    }
    17
    12
    04
    65
    67
    6f
    72
    18
    02
    “egor”.length()
    ‘e’
    ‘g’
    ‘o’
    ‘r’

    View full-size slide

  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;
    }
    }
    6f
    72
    18
    02
    Tag: occupation(3)
    Field encoding: VARINT(0)
    0000 0011
    << 3
    0001 1000
    | 0
    0001 1000
    ==
    18

    View full-size slide

  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;
    }
    }
    72
    18
    02 occupation = PRESIDENT

    View full-size slide

  25. Backwards compatibility

    View full-size slide

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

    View full-size slide

  27. 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

    View full-size slide

  28. 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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  33. 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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  38. // 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;
    */

    View full-size slide

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

    View full-size slide

  40. 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;

    View full-size slide

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

    View full-size slide

  42. */
    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;

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  46. gRPC support
    Kotlin code generation
    Gradle plugin

    View full-size slide

  47. gRPC support

    View full-size slide

  48. What RPC are you ?

    View full-size slide

  49. service RouteGuide {a
    }b

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  52. 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

    View full-size slide

  53. 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

    View full-size slide

  54. 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

    View full-size slide

  55. Which side are you on ?

    View full-size slide

  56. rpcRole = 'client or server?'

    View full-size slide

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

    View full-size slide

  58. rpcCallStyle = 'suspending or blocking?'

    View full-size slide

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

    View full-size slide

  60. singleMethodServices = true || false

    View full-size slide

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

    View full-size slide

  62. gRPC codegen

    View full-size slide

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

    View full-size slide

  64. 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

    View full-size slide

  65. 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

    View full-size slide

  66. 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

    View full-size slide

  67. 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

    View full-size slide

  68. 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

    View full-size slide

  69. 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

    View full-size slide

  70. 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)

    View full-size slide

  71. 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

    View full-size slide

  72. 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'
    }

    View full-size slide

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

    View full-size slide

  74. 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

    View full-size slide

  75. 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

    View full-size slide

  76. 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

    View full-size slide

  77. 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()
    }

    View full-size slide

  78. 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

    View full-size slide

  79. 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

    View full-size slide

  80. 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()
    }

    View full-size slide

  81. 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

    View full-size slide

  82. 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

    View full-size slide

  83. 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

    View full-size slide

  84. 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'
    }

    View full-size slide

  85. Kotlin coroutine or not Kotlin coroutine ?

    View full-size slide

  86. Kotlin Codegen

    View full-size slide

  87. 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

    View full-size slide

  88. Data classes, but not quite!

    View full-size slide

  89. 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

    View full-size slide

  90. 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"""

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  94. 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

    View full-size slide

  95. 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) +

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  99. 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 {

    View full-size slide

  100. 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

    View full-size slide

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

    View full-size slide

  102. Bonus: multiplatform runtime!*
    * - coming soon

    View full-size slide

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

    View full-size slide

  104. Gradle Plugin

    View full-size slide

  105. 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

    View full-size slide

  106. 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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  109. WIRE 3
    Real World Business Use Case Demo

    View full-size slide

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

    View full-size slide

  111. What's next ?

    View full-size slide

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

    View full-size slide

  113. 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

    View full-size slide