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

My journey with Kotlin

My journey with Kotlin

Alexis Seigneurin

December 08, 2017
Tweet

More Decks by Alexis Seigneurin

Other Decks in Technology

Transcript

  1. (My journey with)
    Kotlin
    Alexis Seigneurin

    View Slide

  2. My background
    • VB → Perl → C → C++ → Java → C# → Java → Scala
    • Statically typed languages (except Perl)
    • Almost exclusively using Scala for 3 years

    View Slide

  3. Why I looked at Kotlin
    • Now contributing to a Java project
    • … and it hurts!
    → I want to write useful code (not generate code)

    View Slide

  4. What hurts (me) in Java

    View Slide

  5. POJOs
    • Getters/setters
    • Can intercept assignation/retrieval
    • No one does that in practice → Boilerplate code
    • Convention for naming getters/setters
    • Not enforced / may not be respected
    • Getting/setting values = method calls :-(
    • Not using ‘=‘
    • Step-by-step debugging is unnecessarily complex
    • Have to write equals() / hashCode()
    • Not immutable

    View Slide

  6. POJOs on my project
    $ wc -l src/main/java/com/…/**/*.java | tail -n 1
    2059 total
    $ wc -l src/main/java/com/…/dto/*.java | tail -n 1
    485 total
    $ wc -l src/main/java/com/…/model/*.java | tail -n 1
    105 total
    • → 590 / 2059 lines = 29% boilerplate code
    • Code reviews…

    View Slide

  7. No type inference (or so few)
    NewTopic newTopic = new NewTopic(topicName,
    topicParameters.getNumPartitions(),
    topicParameters.getReplicationFactor());
    Duplicated type
    Method calls… just to get values
    Parsers no longer need
    semicolons (this is 2017)

    View Slide

  8. Streams API is verbose
    List userAclBindings = aclBindings.stream()
    .filter(aclBinding -> aclBinding.entry().principal().equals(filterUser))
    .collect(Collectors.toList());
    Boilerplate
    Boilerplate

    View Slide

  9. No operator overloading
    List userAclBindings = aclBindings.stream()
    .filter(aclBinding -> aclBinding.entry().principal().equals(filterUser))
    .collect(Collectors.toList());
    how about?
    == for object equality
    === for reference equality

    View Slide

  10. No string interpolation
    MessageFormat.format("{0}://{1}{2}:{3}", securityProtocol, instanceName, domain, port).toString()
    new RuntimeException("Tag [" + tagKey + "] not found")
    (No multiline strings either)

    View Slide

  11. One class per file
    • ClusterDto.java
    public class ClusterDto {
    private String clusterId;
    private NodeDto controller;
    private List nodes;

    • NodeDto.java
    public class NodeDto {
    private int id;
    private String host;
    private int port;
    private String rack;

    View Slide

  12. Define a “function”
    @FunctionalInterface
    public interface KafkaAdminFunction {
    T apply(AdminClient client) throws InterruptedException, ExecutionException;
    }
    Function = interface with one method!

    View Slide

  13. What are our options?

    View Slide

  14. Java 9?
    • Jigsaw, new GC, HTTP 2…
    • Syntax improvements (lol)
    • try-with-resource with a pre-defined variable
    • diamond operator on anonymous inner classes
    • interface with private methods

    View Slide

  15. Java 10?
    • Value types
    • Reified generics
    • …

    View Slide

  16. Scala
    • Extremely powerful
    • Can be overwhelming
    • Not acceptable in my team
    • Scala - Java interop isn’t the best
    • Requires a stop-the-world project migration

    View Slide

  17. Non-JVM language?
    • Language change + ecosystem change
    → Too disruptive

    View Slide

  18. Other JVM language?
    • Clojure → Too functional
    • JRuby, JPython… → Clunky
    • Groovy → Too dynamic
    • …
    • Kotlin → YES!

    View Slide

  19. Kotlin

    View Slide

  20. Kotlin
    • JVM language - Initiated by JetBrains
    • Intent:
    • make the code concise + readable
    • make the code safer than pure Java
    • Made for 100% interoperability with Java
    • Migrate one class at a time
    • Statically typed
    • Compiles faster than Scala (troll)

    View Slide

  21. Maturity?
    • Tooling support
    • Build tools: Maven, Gradle
    • IDE: mature support in IntelliJ IDEA (who cares about Eclipse)
    • Official programming language on Android
    • Extended support for Kotlin in Spring 5
    • (To write more idiomatic code)

    View Slide

  22. Interoperability
    • Migrate one class at a time
    • IntelliJ can convert a class / a package at once
    • Perfect interoperability
    • Call Java from Kotlin (seamless)
    • Call Kotlin from Java (seamless)

    View Slide

  23. Java code
    Kotlin code

    View Slide

  24. Basic syntax
    • Defining a class + methods
    class KafkaDescribeClient(val adminClientFactory: KafkaAdminClientFactory) {
    fun describeCluster(region: String, cluster: String): ClusterDto {

    }
    }
    Function
    name: Type
    Constructor with 1 parameter

    View Slide

  25. Basic syntax
    • Defining an immutable variable
    val region = "us-east-1"
    val cluster: String = "engine"
    val adminClient = KafkaDescribeClient(adminClientFactory)
    • Defining a variable (you don’t really need that)
    var something = 12
    something = 42
    No `new` keyword

    View Slide

  26. Basic syntax
    • No `static` methods
    • Define a function in a file (in a package, no class)
    fun nodeToDto(node: Node): NodeDto {
    return NodeDto(node.id(), node.host(), node.port(), node.rack())
    }
    • Import the functions of the class
    import com.….dto.*
    • Call the function
    nodeToDto(controller)

    View Slide

  27. Basic syntax
    • Reference equality
    ===
    !==
    • Structural equality
    == → equals()
    Note that the `==` operator in Kotlin code is translated into a call to [equals]
    when objects on both sides of the operator are not null.
    • … meaning it’s safe, and `o == null` still works

    View Slide

  28. Strings
    • String comparison: use ‘==’
    if(port == "9092") {
    • String interpolation
    private fun buildUri(securityProtocol: String, instanceName: String, port: String):
    String {
    return "${securityProtocol}://${instanceName}${domain}:${port}"
    }
    • Multi-line strings
    System.err.println("""
    ========== Data checking ==========
    Purpose: checks data quality of messages in a topic.
    - Kafka brokers = ${config.kafkaBrokers}
    - Kafka group ID = ${config.applicationId}
    ...
    """)

    View Slide

  29. Data classes
    • Data class = (immutable) POJO
    data class AclParameters(
    val operation: AclOperation,
    val user: String,
    val hostname: String?,
    val groupId: String?)
    • Create an instance (no `new`)
    val aclParams = AclParameters(AclOperation.READ, "u1", "h1", “g1")
    • equals() / hashCode() / toString() → generated
    • From Java = POJO with a constructor + getters
    • For Jackson: there’s a module

    View Slide

  30. Data classes
    • Immutable → copy if you need to update
    val aclParamsCopy = aclParams.copy(groupId = groupName)
    • Destructuring
    val (operation, user, hostname, groupId) = aclParams
    • Partially destructuring
    val (operation, _, hostname, _) = aclParams

    View Slide

  31. Expressions
    • Evaluating an expression and assigning a variable
    val res = if (operation == CREATE) {
    createAcl(aclParameters)
    } else if (operation == ALTER) {
    alterAcl(aclParameters)
    } else if (operation == DELETE) {
    deleteAcl(aclParameters)
    } else {
    throw RuntimeException("Operation not supported: ${operation}")
    }

    View Slide

  32. Streams
    • Remember this piece of Java code?
    List userAclBindings = aclBindings.stream()
    .filter(aclBinding -> aclBinding.entry().principal().equals(filterUser))
    .collect(Collectors.toList());
    • The same in Kotlin
    val userAclBindings = aclBindings.filter { it.entry().principal() == filterUser }
    Type inference No stream(), no Collector Implicit argument Replaces equals()

    View Slide

  33. Streams
    • Java
    private List buildKafkaUris(List instances, String securityProtocol, String port) {
    return instances.stream()
    .map(i -> getTagOrFail(i, TAG_KEY_NAME))
    .map(name -> buildUri(securityProtocol, name, port))
    .collect(Collectors.toList());
    }
    • Kotlin
    private fun buildKafkaUris(instances: List, securityProtocol: String, port: String): List {
    return instances
    .map { getTag(it, TAG_KEY_NAME) }
    .map { buildUri(securityProtocol, it, port) }
    }
    Null safety through types
    (see further)

    View Slide

  34. Null safety
    • Kotlin tries to eliminate NullPointerExceptions
    • Suffix the type with ‘?’ to declare a nullable var/param
    var s: String? = null
    • Need to test for nulls before being able to use the value
    return s.length
    Error:(37, 17) Kotlin: Only safe (?.) or non-null asserted (!!.) calls are
    allowed on a nullable receiver of type String?
    if (s !=null) {
    return s.length
    }

    View Slide

  35. Null safety
    • Can’t return a potentially null value if not declared as nullable
    private fun getTag(instance: Instance, tagKey: String): String {
    val tag = instance.tags
    .filter { it.key.equals(tagKey, ignoreCase = true) }
    .map { it.value }
    .firstOrNull()
    return tag
    }
    Error:(40, 16) Kotlin: Type mismatch: inferred type is String? but String was expected
    • Safe:
    private fun getTag(instance: Instance, tagKey: String): String {
    val tag = instance.tags
    .filter { it.key.equals(tagKey, ignoreCase = true) }
    .map { it.value }
    .firstOrNull()
    if (tag == null) {
    throw RuntimeException("Tag [${tagKey}] not found")
    }
    return tag
    }
    • (Or just call first() in this case)
    tag is of type String?
    tag is of type String

    View Slide

  36. Null safety
    • Elvis operator
    val hostname = aclParameters.hostname ?: “*”
    • Safe calls
    val length: Int? = s?.length
    Return value is a nullable type

    View Slide

  37. Functions
    • First class support for functions
    protected fun withAdminClient(func: (AdminClient) -> R): R {
    val adminClient = adminClientFactory.createAdminClient()
    return adminClient.use { func(adminClient) }
    }
    A function with one param
    of type AdminClient
    Call it… as a function
    (Bye bye functional interfaces)

    View Slide

  38. Type alias
    • Can alias any type
    typealias Clusters = List
    • Example
    typealias KakfaAdminFunc = (AdminClient) -> R
    protected fun withAdminClient(func: KakfaAdminFunc): R {

    View Slide

  39. Extension functions
    • Extend existing classes with new methods
    • Resolved statically / locally
    • Example
    fun AmazonEC2.findInstances(request: DescribeInstancesRequest): ArrayList {
    val allInstances = ArrayList()
    var token: String?
    do {
    val result = this.describeInstances(request)
    val instances = result.reservations.flatMap { it.instances }
    allInstances.addAll(instances)
    token = result.nextToken
    request.nextToken = token
    } while (token != null)
    return allInstances
    }
    ec2.findInstances(request)
    Class to extend
    Method

    View Slide

  40. Wrapping up

    View Slide

  41. Conclusion
    • Easy to use & intuitive
    • Greater expressivity than Java
    • Simpler than Scala
    • Very positive experience so far

    View Slide

  42. Try it out!
    @aseigneurin

    View Slide