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 Slide

  2. View Slide

  3. View Slide

  4. Why gRPC?

    View Slide

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

    View Slide

  6. Protocol Buffers

    View Slide

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

    View Slide

  8. Schema

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

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

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

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

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

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

    View Slide

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

    View Slide

  17. Bytes

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

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

    View Slide

  20. "proto2";
    com.squareup;
    ava_package = "com.squareup";
    Person {
    ed int32 id = 1;
    al string name = 2;
    al Occupation occupation = 3;
    ccupation {
    WARE_DEVELOPER = 0;
    ENT = 1;
    IDENT = 2;
    0817120465676f721802

    View Slide

  21. 08 17 12 04 65 67 6f 72 18 02
    "proto2";
    com.squareup;
    ava_package = "com.squareup";
    Person {
    ed int32 id = 1;
    al string name = 2;
    al Occupation occupation = 3;
    ccupation {
    WARE_DEVELOPER = 0;
    ENT = 1;
    IDENT = 2;
    ==
    10 bytes

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

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

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  30. Backwards compatibility

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  42. Why WIRE ?

    View Slide

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

    View Slide

  44. View Slide

  45. // Generated by the protocol buffer compiler. DO NOT EDIT!
    // source: com/squareup/person.proto
    package com.squareup;
    public final class PersonOuterClass {
    private PersonOuterClass() {}
    public static void registerAllExtensions(
    com.google.protobuf.ExtensionRegistryLite registry) {
    }
    public static void registerAllExtensions(
    com.google.protobuf.ExtensionRegistry registry) {
    registerAllExtensions(
    (com.google.protobuf.ExtensionRegistryLite) registry);
    }
    public interface PersonOrBuilder extends
    // @@protoc_insertion_point(interface_extends:com.squareup.Person)
    com.google.protobuf.MessageOrBuilder {
    /**
    * required int32 id = 1;
    */
    boolean hasId();
    /**
    * required int32 id = 1;
    */

    View Slide

  46. */
    public static final class Person extends
    com.google.protobuf.GeneratedMessageV3 implements
    // @@protoc_insertion_point(message_implements:com.squareup.Person)
    PersonOrBuilder {
    private static final long serialVersionUID = 0L;
    // Use Person.newBuilder() to construct.
    private Person(com.google.protobuf.GeneratedMessageV3.Builder> builder) {
    super(builder);
    }
    private Person() {
    name_ = "";
    occupation_ = 0;
    }
    @java.lang.Override
    public final com.google.protobuf.UnknownFieldSet
    getUnknownFields() {
    return this.unknownFields;
    }
    private Person(
    com.google.protobuf.CodedInputStream input,
    com.google.protobuf.ExtensionRegistryLite extensionRegistry)
    throws com.google.protobuf.InvalidProtocolBufferException {
    this();
    if (extensionRegistry == null) {
    throw new java.lang.NullPointerException();
    }

    View Slide

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

    View Slide

  48. public Occupation findValueByNumber(int number) {
    return Occupation.forNumber(number);
    }
    };
    public final com.google.protobuf.Descriptors.EnumValueDescriptor
    getValueDescriptor() {
    return getDescriptor().getValues().get(ordinal());
    }
    public final com.google.protobuf.Descriptors.EnumDescriptor
    getDescriptorForType() {
    return getDescriptor();
    }
    public static final com.google.protobuf.Descriptors.EnumDescriptor
    getDescriptor() {
    return com.squareup.PersonOuterClass.Person.getDescriptor().getEnumTypes().get(0);
    }
    private static final Occupation[] VALUES = values();
    public static Occupation valueOf(
    com.google.protobuf.Descriptors.EnumValueDescriptor desc) {
    if (desc.getType() != getDescriptor()) {
    throw new java.lang.IllegalArgumentException(
    "EnumValueDescriptor is not for this type.");
    }
    return VALUES[desc.getIndex()];

    View Slide

  49. */
    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 Slide

  50. }
    if (!(obj instanceof com.squareup.PersonOuterClass.Person)) {
    return super.equals(obj);
    }
    com.squareup.PersonOuterClass.Person other = (com.squareup.PersonOuterClass.Person) obj
    if (hasId() != other.hasId()) return false;
    if (hasId()) {
    if (getId()
    != other.getId()) return false;
    }
    if (hasName() != other.hasName()) return false;
    if (hasName()) {
    if (!getName()
    .equals(other.getName())) return false;
    }
    if (hasOccupation() != other.hasOccupation()) return false;
    if (hasOccupation()) {

    View Slide

  51. View Slide

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

    View Slide

  53. View Slide

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

    View Slide

  55. WIRE 3

    View Slide

  56. gRPC support
    Kotlin code generation
    Gradle plugin

    View Slide

  57. gRPC support

    View Slide

  58. What RPC are you ?

    View Slide

  59. service RouteGuide {a
    }b

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  65. Which side are you on ?

    View Slide

  66. rpcRole = 'client or server?'

    View Slide

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

    View Slide

  68. rpcCallStyle = 'suspending or blocking?'

    View Slide

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

    View Slide

  70. singleMethodServices = true || false

    View Slide

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

    View Slide

  72. gRPC codegen

    View Slide

  73. Client Side
    service RouteGuide {a
    rpc GetFeature(Point) returns (Feature) {}
    rpc ListFeatures(Rectangle) returns (stream Feature) {}
    rpc RecordRoute(stream Point) returns (RouteSummary) {}
    rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
    }b
    wire {
    kotlin {
    rpcCallStyle = 'suspending'
    rpcRole = 'client'
    singleMethodServices = false
    }
    }

    View Slide

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

    View Slide

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

    View Slide

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

  77. 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 Slide

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

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

  80. 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 Slide

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

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

  83. Server Side
    service RouteGuide {a
    rpc GetFeature(Point) returns (Feature) {}
    rpc ListFeatures(Rectangle) returns (stream Feature) {}
    rpc RecordRoute(stream Point) returns (RouteSummary) {}
    rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
    }b
    wire {
    kotlin {
    rpcCallStyle = 'blocking'
    rpcRole = 'server'
    singleMethodServices = true
    }
    }

    View Slide

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

    View Slide

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

    View Slide

  86. 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 Slide

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

  88. 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 Slide

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

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

  91. 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 Slide

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

  93. 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 Slide

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

  95. Kotlin coroutine or not Kotlin coroutine ?

    View Slide

  96. Kotlin Codegen

    View 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 Slide

  98. Data classes, but not quite!

    View Slide

  99. class Person(
    @field:WireField(
    tag = 1,
    adapter = "com.squareup.wire.ProtoAdapter#INT32",
    label = WireField.Label.REQUIRED
    )
    val id: Int,
    @field:WireField(
    tag = 2,
    adapter = "com.squareup.wire.ProtoAdapter#STRING"
    )
    val name: String? = null,
    @field:WireField(
    tag = 3,
    adapter = "com.squareup.Person${'$'}Occupation#ADAPTER"
    )
    val occupation: Occupation? = null,
    unknownFields: ByteString = ByteString.EMPTY
    ) : Message(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 Slide

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

  101. && name == other.name
    && occupation == other.occupation
    }
    override fun hashCode(): Int {
    var result = super.hashCode
    if (result == 0) {
    result = id.hashCode()
    result = result * 37 + name.hashCode()
    result = result * 37 + occupation.hashCode()
    super.hashCode = result
    }
    return result
    }
    override fun toString(): String {
    val result = mutableListOf()
    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 Slide

  102. }
    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 Slide

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

    View Slide

  104. class Person(
    @field:WireField(
    tag = 1,
    adapter = "com.squareup.wire.ProtoAdapter#INT32",
    label = WireField.Label.REQUIRED
    )
    val id: Int,
    @field:WireField(
    tag = 2,
    adapter = "com.squareup.wire.ProtoAdapter#STRING"
    )
    val name: String? = null,
    @field:WireField(
    tag = 3,
    adapter = "com.squareup.Person${'$'}Occupation#ADAPTER"
    )
    val occupation: Occupation? = null,
    unknownFields: ByteString = ByteString.EMPTY
    ) : Message(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 Slide

  105. if (occupation != null) result += """occupation=$occupation"""
    return result.joinToString(prefix = "Person{", separator = ", ", postfix = "}")
    }
    @Deprecated(
    message = "Shouldn't be used in Kotlin",
    level = DeprecationLevel.HIDDEN
    )
    override fun newBuilder(): Nothing {
    throw AssertionError()
    }
    fun copy(
    id: Int = this.id,
    name: String? = this.name,
    occupation: Occupation? = this.occupation,
    unknownFields: ByteString = this.unknownFields
    ): Person = Person(id, name, occupation, unknownFields)
    companion object {
    @JvmField
    val ADAPTER: ProtoAdapter = 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 Slide

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

    View Slide

  107. class Person(
    @field:WireField(
    tag = 1,
    adapter = "com.squareup.wire.ProtoAdapter#INT32",
    label = WireField.Label.REQUIRED
    )
    val id: Int,
    @field:WireField(
    tag = 2,
    adapter = "com.squareup.wire.ProtoAdapter#STRING"
    )
    val name: String? = null,
    @field:WireField(
    tag = 3,
    adapter = "com.squareup.Person${'$'}Occupation#ADAPTER"
    )
    val occupation: Occupation? = null,
    unknownFields: ByteString = ByteString.EMPTY
    ) : Message(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 Slide

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

    View Slide

  109. class Person(
    @field:WireField(
    tag = 1,
    adapter = "com.squareup.wire.ProtoAdapter#INT32",
    label = WireField.Label.REQUIRED
    )
    @JvmField
    val id: Int,
    @field:WireField(
    tag = 2,
    adapter = "com.squareup.wire.ProtoAdapter#STRING"
    )
    @JvmField
    val name: String? = null,
    @field:WireField(
    tag = 3,
    adapter = "com.squareup.Person${'$'}Occupation#ADAPTER"
    )
    @JvmField
    val occupation: Occupation? = null,
    unknownFields: ByteString = ByteString.EMPTY
    ) : Message(ADAPTER, unknownFields) {
    override fun equals(other: Any?): Boolean {

    View Slide

  110. if (occupation != null) result += """occupation=$occupation"""
    return result.joinToString(prefix = "Person{", separator = ", ", postfix = "}")
    }
    override fun newBuilder(): Builder {
    val builder = Builder()
    builder.id = id
    builder.name = name
    builder.occupation = occupation
    builder.addUnknownFields(unknownFields)
    return builder
    }
    fun copy(
    id: Int = this.id,
    name: String? = this.name,
    occupation: Occupation? = this.occupation,
    unknownFields: ByteString = this.unknownFields
    ): Person = Person(id, name, occupation, unknownFields)
    class Builder : Message.Builder() {
    @JvmField
    var id: Int? = null
    @JvmField
    var name: String? = null
    @JvmField

    View Slide

  111. 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 Slide

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

    View Slide

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

    View Slide

  114. View Slide

  115. Gradle Plugin

    View Slide

  116. tasks.register("generateProtos", JavaExec) {
    classpath = configurations.wire
    main = 'com.squareup.wire.WireCompiler'
    args = [
    "--proto_path=$rootDir/protos/src/main/proto",
    '--kotlin_out=src/main/java',
    '--includes=com.squareup.wire.whiteboard.Whiteboard',
    ]
    }
    Wire < 3.0

    View Slide

  117. apply plugin: 'com.squareup.wire'
    wire {
    sourcePath {
    srcDir "$rootDir/protos/src/main/proto"
    }
    kotlin {
    out "src/main/java"
    includes = ['com.squareup.wire.whiteboard.Whiteboard']
    }
    }
    WIRE 3

    View Slide

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

    View Slide

  119. wire {
    kotlin {
    includes = ['com.example.pizza.*']
    excludes = ['com.example.sales.*']
    exclusive = false
    out "${buildDir}/custom"
    android = true
    javaInterop = true
    rpcCallStyle = 'blocking'
    rpcRole = 'server'
    singleMethodServices = true
    }
    }

    View Slide

  120. WIRE 3
    Real World Business Use Case Demo

    View Slide

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

    View Slide

  122. What's next ?

    View Slide

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

    View Slide

  124. WIRE III

    View Slide

  125. References
    • gRPC official website: https://grpc.io
    • Protocol Buffers documentations: https://developers.google.com/protocol-buffers/
    • Wire documentations: https://square.github.io/wire/
    • Wire source code repository: https://github.com/square/wire/
    • Misk microservice container: https://github.com/cashapp/misk

    View Slide