Kotlin – your 2017 Java replacement

Kotlin – your 2017 Java replacement

Do you dream about life without unexpected nulls? Do you fancy a little bit of functional programming? Are you tired of Java ceremony?

Then maybe 2017 is The Right Moment to choose Kotlin! This JVM language brings you stuff like null-safety and immutability by default. Less boilerplate, more safety. And you can introduce it in your project class after class, module after module, without risky revolution.

I will show you pros and cons of Kotlin, based on last 8 months of my experience. In November 2016 I started a new backend project built on top of Gradle and Spring. Today I still do not regret the decision to write it in Kotlin. Let's see if I convince you! :-)

A246cc5f7f99987d6a33947485e69b15?s=128

Paweł Barszcz

June 21, 2017
Tweet

Transcript

  1. Kotlin your 2017 Java replacement

  2. ?

  3. goo.gl/maps/j4braT8Ucgv

  4. goo.gl/maps/4aSgyZRdnKq ⬅ Java Kotlin ➡

  5. None
  6. contribution over time github.com/JetBrains/kotlin/graphs/contributors

  7. www.tiobe.com/tiobe-index

  8. my story

  9. Vavr - Functional Java Done Right
 Grzegorz Piwowarek @ Confitura

    2016 more „classic” projects greenfield project
 solo on backend
 head full of ideas April
 2016 October
 2016 Kotlin here, Kotlin there
  10. start.spring.io/#!type=gradle-project&language=kotlin

  11. stack Kotlin 1.0 –> 1.1 JUnit 4
 - AssertJ
 -

    JUnitParams Spring Boot 1.3 –> 1.5
 - web
 - security
 - jdbc
 - test
  12. 8 months later… Kotlin in the whole service team of

    2 devs 3 months on (real) production still no regret
  13. first impressions

  14. syntax

  15. class Task( val id: TaskId, val name: String, private val

    priority: TaskPriority = NORMAL ) { fun isImportant() = when (priority) { HIGH -> true NORMAL, LOW -> false } fun duplicate(): Task { val newName = "$name (copy)" /* ... */ } } Task(name = "prepare a talk for Devoxx PL", id = TaskId.random())
  16. class Task( val id: TaskId, val name: String, private val

    priority: TaskPriority = NORMAL ) { fun isImportant() = when (priority) { HIGH -> true NORMAL, LOW -> false } fun duplicate(): Task { val newName = "$name (copy)" /* ... */ } } Task(name = "prepare a talk for Devoxx PL", id = TaskId.random()) name –> type
  17. class Task( val id: TaskId, val name: String, private val

    priority: TaskPriority = NORMAL ) { fun isImportant() = when (priority) { HIGH -> true NORMAL, LOW -> false } fun duplicate(): Task { val newName = "$name (copy)" /* ... */ } } Task(name = "prepare a talk for Devoxx PL", id = TaskId.random()) fields declared as
 constructor arguments
  18. class Task( val id: TaskId, val name: String, private val

    priority: TaskPriority = NORMAL ) { fun isImportant() = when (priority) { HIGH -> true NORMAL, LOW -> false } fun duplicate(): Task { val newName = "$name (copy)" /* ... */ } } Task(name = "prepare a talk for Devoxx PL", id = TaskId.random()) defaults
  19. class Task( val id: TaskId, val name: String, private val

    priority: TaskPriority = NORMAL ) { fun isImportant() = when (priority) { HIGH -> true NORMAL, LOW -> false } fun duplicate(): Task { val newName = "$name (copy)" /* ... */ } } Task(name = "prepare a talk for Devoxx PL", id = TaskId.random()) named parameters
  20. class Task( val id: TaskId, val name: String, private val

    priority: TaskPriority = NORMAL ) { fun isImportant() = when (priority) { HIGH -> true NORMAL, LOW -> false } fun duplicate(): Task { val newName = "$name (copy)" /* ... */ } } Task(name = "prepare a talk for Devoxx PL", id = TaskId.random()) String interpolation
  21. class Task( val id: TaskId, val name: String, private val

    priority: TaskPriority = NORMAL ) { fun isImportant() = when (priority) { HIGH -> true NORMAL, LOW -> false } fun duplicate(): Task { val newName = "$name (copy)" /* ... */ } } Task(name = "prepare a talk for Devoxx PL", id = TaskId.random()) no type, because…
 type inference
  22. class Task( val id: TaskId, val name: String, private val

    priority: TaskPriority = NORMAL ) { fun isImportant() = when (priority) { HIGH -> true NORMAL, LOW -> false } fun duplicate(): Task { val newName = "$name (copy)" /* ... */ } } Task(name = "prepare a talk for Devoxx PL", id = TaskId.random()) smart `when` compiles
 if all options are covered
  23. class Task( val id: TaskId, val name: String, private val

    priority: TaskPriority = NORMAL ) { fun isImportant() = when (priority) { HIGH -> true NORMAL, LOW -> false } fun duplicate(): Task { val newName = "$name (copy)" /* ... */ } } Task(name = "prepare a talk for Devoxx PL", id = TaskId.random()) expressions everywhere
 `if`, `when`, `try`…
  24. class Task( val id: TaskId, val name: String, private val

    priority: TaskPriority = NORMAL ) { fun isImportant() = when (priority) { HIGH -> true NORMAL, LOW -> false } fun duplicate(): Task { val newName = "$name (copy)" /* ... */ } } Task(name = "prepare a talk for Devoxx PL", id = TaskId.random()) function as a single expression
  25. immutability

  26. var map1: Map<String, Int> = mapOf("A" to 1) val map2:

    MutableMap<String, Int> = mutableMapOf("B" to 2) fun whatever() { map1 += ("A" to 111) map2["B"] = 222 } modification
 mutable collections only shorter = immutable
  27. var map1: Map<String, Int> = mapOf("A" to 1) val map2:

    MutableMap<String, Int> = mutableMapOf("B" to 2) fun whatever() { map1 += ("A" to 111) map2["B"] = 222 } reassignment
 `var` only var ≠ val
  28. data classes

  29. data class Color( val red: Pigment, val green: Pigment, val

    blue: Pigment, val alpha: Alpha = Alpha.none() ) { fun transparent() = copy(alpha = Alpha.medium()) } out of the box: - copy
  30. data class Color( val red: Pigment, val green: Pigment, val

    blue: Pigment, val alpha: Alpha = Alpha.none() ) { fun transparent() = copy(alpha = Alpha.medium()) } val (red, green, blue, alpha) = myColor out of the box: - copy - destructuring
  31. data class Color( val red: Pigment, val green: Pigment, val

    blue: Pigment, val alpha: Alpha = Alpha.none() ) { fun transparent() = copy(alpha = Alpha.medium()) } out of the box: - copy - destructuring - equal & hashCode - toString
  32. Kotlin + Spring

  33. Kotlin + Spring works OK, but…

  34. everything is final • Java –> „final” on demand
 Kotlin

    –> „open” on demand • Spring, Mockito, … how to proxy? • kotlin-allopen / kotlin-spring
 blog.jetbrains.com/kotlin/2016/12/kotlin-1-0-6-is-here
  35. buildscript { dependencies { classpath "org.jetbrains.kotlin:kotlin-allopen:1.1.2-5" } } apply plugin:

    "kotlin-allopen" allOpen { annotation("com.your.Annotation") } everything is final
  36. buildscript { dependencies { classpath "org.jetbrains.kotlin:kotlin-allopen:1.1.2-5" } } apply plugin:

    "kotlin-spring" everything is final
  37. killer feature

  38. (definitely not) William Shakespeare „can be null,
 or
 cannot be

    null”
  39. null-safety

  40. null-safety

  41. interface ProjectRepository { fun createProject(name: ProjectName): Project fun findProjectBy(id: ProjectId):

    Project? } null? nope can be null
  42. val importantTasks = projectRepository .findProjectBy(projectId) ?.tasks ?.filter { it.isImportant() }

    ?: emptyList() ?
  43. val importantTasks = projectRepository .findProjectBy(projectId) ?.tasks ?.filter { it.isImportant() }

    ?: emptyList() ? Project
 or null
  44. val importantTasks = projectRepository .findProjectBy(projectId) ?.tasks ?.filter { it.isImportant() }

    ?: emptyList() ? Project's Tasks
 or null
  45. val importantTasks = projectRepository .findProjectBy(projectId) ?.tasks ?.filter { it.isImportant() }

    ?: emptyList() ? Project's important Tasks
 or null
  46. val importantTasks = projectRepository .findProjectBy(projectId) ?.tasks ?.filter { it.isImportant() }

    ?: emptyList() ? fallback for null
  47. val project: Project? = projectRepository.findProjectBy(projectId) return project?.tasks compiler is smart

  48. val project: Project? = projectRepository.findProjectBy(projectId) if (project == null) {

    throw ProjectNotFound() } return project.tasks compiler is smart
  49. @Autowired var projectRepository: ProjectRepository? = null @Autowired nullable ☹

  50. @Autowired var projectRepository: ProjectRepository? = null @Autowired lateinit var projectRepository:

    ProjectRepository @Autowired there is no null
 to handle
  51. @Override public void configure(WebSecurity webSecurity) { webSecurity .ignoring() .mvcMatchers(/* ...

    */); } when null comes from Java
  52. @Override public void configure(WebSecurity webSecurity) { webSecurity .ignoring() .mvcMatchers(/* ...

    */); } when null comes from Java
  53. override fun configure(webSecurity: WebSecurity) { webSecurity .ignoring() .mvcMatchers(/* ... */)

    } override fun configure(webSecurity: WebSecurity?) { webSecurity!! .ignoring() .mvcMatchers(/* ... */) } override fun configure4(webSecurity: WebSecurity?) { webSecurity ?.ignoring() ?.mvcMatchers(/* ... */) } when null comes from Java
  54. override fun configure(webSecurity: WebSecurity) { webSecurity .ignoring() .mvcMatchers(/* ... */)

    } override fun configure(webSecurity: WebSecurity?) { webSecurity!! .ignoring() .mvcMatchers(/* ... */) } override fun configure4(webSecurity: WebSecurity?) { webSecurity ?.ignoring() ?.mvcMatchers(/* ... */) } when null comes from Java
  55. override fun configure(webSecurity: WebSecurity) { webSecurity .ignoring() .mvcMatchers(/* ... */)

    } override fun configure(webSecurity: WebSecurity?) { webSecurity!! .ignoring() .mvcMatchers(/* ... */) } override fun configure4(webSecurity: WebSecurity?) { webSecurity ?.ignoring() ?.mvcMatchers(/* ... */) } when null comes from Java
  56. override fun configure(webSecurity: WebSecurity) { webSecurity .ignoring() .mvcMatchers(/* ... */)

    } override fun configure(webSecurity: WebSecurity?) { webSecurity!! .ignoring() .mvcMatchers(/* ... */) } override fun configure4(webSecurity: WebSecurity?) { webSecurity ?.ignoring() ?.mvcMatchers(/* ... */) } when null comes from Java
  57. we need to go deeper

  58. what about…

  59. …request/response bodies? • if you want JSON structure declared explicitly

  60. class ActiveProjectsJson( val projects: List<ProjectJson> ) class ArchivedProjectsJson( val archivedProjects:

    List<ProjectJson> ) class ProjectJson( val id: String, val name: String, val color: String ) class ProjectUpdateJson( val name: String?, val color: String? ) …request/response bodies?
  61. class ActiveProjectsJson( val projects: List<ProjectJson> ) class ArchivedProjectsJson( val archivedProjects:

    List<ProjectJson> ) class ProjectJson( val id: String, val name: String, val color: String ) class ProjectUpdateJson( val name: String?, val color: String? ) …request/response bodies?
  62. class ActiveProjectsJson( val projects: List<ProjectJson> ) class ArchivedProjectsJson( val archivedProjects:

    List<ProjectJson> ) class ProjectJson( val id: String, val name: String, val color: String ) class ProjectUpdateJson( val name: String?, val color: String? ) …request/response bodies? in one file
 if you prefer
  63. compile 'com.fasterxml.jackson.module:jackson-module-kotlin' …request/response bodies?

  64. what about…

  65. …working with JSONs? • when you don't want strict mapping

    into types • eg. in tests
  66. Kotson • github.com/SalomonBrys/Kotson • Gson in a Kotlin way

  67. jsonObject( "name" to "John", "age" to 29, "phoneNumbers" to jsonArray(

    jsonObject( "type" to "personal", "number" to "123 456 789" ), jsonObject( "type" to "office", "number" to "987 654 321" ) ) ).toString() Kotson
  68. personJson["phoneNumbers"].asJsonArray .first { it["type"].asString == "office" } .let { it["number"].asString

    } Kotson
  69. what about…

  70. …SQL? • me & Hibernate = • me & tables

    coupled with domain entities = • me & types = ❤
  71. Exposed • github.com/JetBrains/Exposed • simple SQL typed abstraction
 good enough

    for simple cases
  72. object ProjectsTable : Table("projects") { val id = long("id").autoIncrement().primaryKey() val

    ownerId = (long("owner") references UsersTable.id) val name = varchar("name", 50) val isArchived = bool("is_archived").default(false) } Exposed
  73. SchemaUtils.create(ProjectsTable) Exposed

  74. ProjectsTable .select { ProjectsTable.id eq projectId } .map { row

    -> Project( id = row[ProjectsTable.id], name = row[ProjectsTable.name], isArchived = row[ProjectsTable.isArchived] ) } .firstOrNull() Exposed
  75. compile 'org.jetbrains.exposed:spring-transaction' //... 
 import org.jetbrains.exposed.spring.SpringTransactionManager @Configuration @EnableTransactionManagement class TransactionConfiguration

    { @Bean fun transactionManager(dataSource: DataSource) = SpringTransactionManager(dataSource) } Exposed
  76. what about…

  77. …functional constructs? • currying • function composition • Either •

    memoization • partial application • Try
  78. funKTionale • github.com/MarioAriasC/funKTionale • functional constructs and patterns for Kotlin

  79. return Try { fetchProjectsOverHttp() // may throw an exception }.handle

    { throw ConnectionFailed() }.onEach { response -> logger.info { response } }.flatMap { response -> Try { parse(response) } .handle { throw ResponseParsingFailed() } }.fold({ result -> result.projects },
 { exception -> throw exception })
  80. return Try { fetchProjectsOverHttp() // may throw an exception }.handle

    { throw ConnectionFailed() }.onEach { response -> logger.info { response } }.flatMap { response -> Try { parse(response) } .handle { throw ResponseParsingFailed() } }.fold({ result -> result.projects },
 { exception -> throw exception }) wrap value in Try.Sucess
 wrap exception in Try.Failure
  81. return Try { fetchProjectsOverHttp() // may throw an exception }.handle

    { throw ConnectionFailed() }.onEach { response -> logger.info { response } }.flatMap { response -> Try { parse(response) } .handle { throw ResponseParsingFailed() } }.fold({ result -> result.projects },
 { exception -> throw exception }) do something
 with Try.Failure
  82. return Try { fetchProjectsOverHttp() // may throw an exception }.handle

    { throw ConnectionFailed() }.onEach { response -> logger.info { response } }.flatMap { response -> Try { parse(response) } .handle { throw ResponseParsingFailed() } }.fold({ result -> result.projects },
 { exception -> throw exception }) do something
 with Try.Success
  83. return Try { fetchProjectsOverHttp() // may throw an exception }.handle

    { throw ConnectionFailed() }.onEach { response -> logger.info { response } }.flatMap { response -> Try { parse(response) } .handle { throw ResponseParsingFailed() } }.fold({ result -> result.projects },
 { exception -> throw exception }) nested Try logic
 inside `flatMap`
  84. return Try { fetchProjectsOverHttp() // may throw an exception }.handle

    { throw ConnectionFailed() }.onEach { response -> logger.info { response } }.flatMap { response -> Try { parse(response) } .handle { throw ResponseParsingFailed() } }.fold({ result -> result.projects },
 { exception -> throw exception }) unwrap value or exception
  85. and if I want modularization…

  86. Kotlin + Gradle internal scope multi-project build

  87. each sub-project is a separate module with one common
 parent

    project
  88. private (default) package-private protected public private protected internal public (default)

  89. private (default) package-private protected public private protected internal public (default)

    visible within
 module
  90. one more minor thing…

  91. Mockito.`when`(…) `backquotes`

  92. @Test fun `User cannot archive Project owned by another User`()

    { // ... } `backquotes in tests ✌`
  93. final thoughts

  94. • maintained by JetBrains • easy to start with •

    nice syntax, immutability, data classes • works well with Spring • Kotson, Exposed, funKTionale • null-safety • low risk, pragmatic approach
  95. • maintained by JetBrains • easy to start with •

    nice syntax, immutability, data classes • works well with Spring • Kotson, Exposed, funKTionale • null-safety • low risk, pragmatic approach
  96. interoperability • using Java inside Kotlin
 kotlinlang.org/docs/reference/java-interop.html • using Kotlin

    inside Java
 kotlinlang.org/docs/reference/java-to-kotlin-interop.html
  97. None
  98. want more? • type aliases • couroutines (async/await/yield) • lazy

    sequences • sealed classes • gradle-script-kotlin • …
  99. • Spring Boot 1.5 + Kotlin + customized Exposed (blog

    post + git repo)
 spring.io/blog/2016/03/20/a-geospatial-messenger-with-kotlin-spring-boot-and-postgresql • functional Spring 5 + Kotlin (blog post + git repo)
 spring.io/blog/2017/01/04/introducing-kotlin-support-in-spring-framework-5-0 • reference 
 kotlinlang.org/docs/reference • links, libs, blogs…
 kotlin.link • community 
 slack.kotlinlang.org • these slides 
 speakerdeck.com/nkoder/kotlin-your-2017-java-replacement
  100. try Kotlin be productive have fun

  101. Paweł Barszcz @nkoder pawelbarszcz@gmail.com timbercode.pl