$30 off During Our Annual Pro Sale. View Details »

SpringIO 2022 - Spring extensions for Kotlin

SpringIO 2022 - Spring extensions for Kotlin

Anton Arhipov

June 10, 2022
Tweet

More Decks by Anton Arhipov

Other Decks in Programming

Transcript

  1. @antonarhipov Spring extensions for Kotlin

  2. @antonarhipov

  3. +

  4. None
  5. None
  6. None
  7. import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication … @SpringBootApplication class DemoApplication fun main(args:

    Array<String>) { runApplication<DemoApplication>(*args) }
  8. import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication … @SpringBootApplication class DemoApplication fun main(args:

    Array<String>) { runApplication<DemoApplication>(*args) }
  9. import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication … @SpringBootApplication class DemoApplication fun main(args:

    Array<String>) { runApplication<DemoApplication>(*args) }
  10. import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication … @SpringBootApplication class DemoApplication fun main(args:

    Array<String>) { runApplication<DemoApplication>(*args) } @RestController class MessageResource(val service: MessageService) { @GetMapping fun index(): List<Message> = service.findMessages() @PostMapping fun post(@RequestBody message: Message) { service.post(message) } }
  11. @Service class MessageService(val db: MessageRepository) { fun findMessages(): List<Message> =

    db.findMessages() fun post(message: Message){ db.save(message) } } interface MessageRepository : CrudRepository<Message, String>{ @Query("select * from messages") fun findMessages(): List<Message> } @Table("MESSAGES") data class Message(@Id val id: String?, val text: String)
  12. @Service class MessageService(val db: MessageRepository) { fun findMessages(): List<Message> =

    db.findMessages() fun post(message: Message){ db.save(message) } } interface MessageRepository : CrudRepository<Message, String> { @Query("select * from messages") fun findMessages(): List<Message> } @Table("MESSAGES") data class Message(@Id val id: String?, val text: String)
  13. @Service class MessageService(val db: MessageRepository) { fun findMessages(): List<Message> =

    db.findMessages() fun post(message: Message){ db.save(message) } } interface MessageRepository : CrudRepository<Message, String>{ @Query("select * from messages") fun findMessages(): List<Message> } @Table("MESSAGES") data class Message(@Id val id: String?, val text: String)
  14. @Service class MessageService(val db: MessageRepository) { fun findMessages(): List<Message> =

    db.findMessages() fun post(message: Message){ db.save(message) } } interface MessageRepository : CrudRepository<Message, String>{ @Query("select * from messages") fun findMessages(): List<Message> } @Table("MESSAGES") data class Message(@Id val id: String?, val text: String) Not to be used with JPA!
  15. None
  16. https://stackover fl ow.com/questions/58127353/should-i-use-kotlin-data-class-as-jpa-entity Resources: https://kotlinexpertise.com/hibernate-with-kotlin-spring-boot/ https://dzone.com/articles/kotlin-data-classes-and-jpa https://blog.codecentric.de/en/2017/06/kotlin-spring-working-jpa-data-classes/ https://spring.io/guides/tutorials/spring-boot-kotlin/

  17. Important for starters

  18. Important for starters * Kotlin classes are fi nal by

    default
  19. Important for starters * Kotlin classes are fi nal by

    default * Compiler plugins to the rescue!
  20. Important for starters * Kotlin classes are fi nal by

    default * Compiler plugins to the rescue! * Data classes don’t play well with JPA
  21. Important for starters * Kotlin classes are fi nal by

    default * Compiler plugins to the rescue! * Data classes don’t play well with JPA * Use normal classes with JPA
  22. * Kotlin classes are fi nal by default * Compiler

    plugins to the rescue! * Data classes don’t play well with JPA * Use normal classes with JPA Important for starters * Use start.spring.io to generate con fi gs
  23. kotlin extensions

  24. None
  25. None
  26. None
  27. println("hello".uuid())

  28. fun String.uuid(): String = UUID.nameUUIDFromBytes(this.encodeToByteArray()).toString() println("hello".uuid())

  29. fun String.uuid(): String = UUID.nameUUIDFromBytes(encodeToByteArray()).toString() println("hello".uuid())

  30. fun String.uuid() = UUID.nameUUIDFromBytes(encodeToByteArray()).toString() println("hello".uuid())

  31. @Service class MessageService(val db: JdbcTemplate) { fun findMessages(): List<Message> =

    db.query("select * from messages") { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun findMessageById(id: String): List<Message> = db.query("select * from messages where id = ?", id) { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun post(message: Message){ db.update("insert into messages values ( ?, ? )", message.id ?: message.text.uuid(), message.text) } }
  32. @Service class MessageService(val db: JdbcTemplate) { fun findMessages(): List<Message> =

    db.query("select * from messages") { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun findMessageById(id: String): List<Message> = db.query("select * from messages where id = ?", id) { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun post(message: Message){ db.update("insert into messages values ( ?, ? )", message.id ?: message.text.uuid(), message.text) } }
  33. @Service class MessageService(val db: JdbcTemplate) { fun findMessages(): List<Message> =

    db.query("select * from messages") { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun findMessageById(id: String): List<Message> = db.query("select * from messages where id = ?", id) { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun post(message: Message){ db.update("insert into messages values ( ?, ? )", message.id ?: message.text.uuid(), message.text) } } fun findMessageById(id: String): List<Message> = db.query("select * from messages where id = ?", RowMapper { rs, _ -> Message(rs.getString("id"), rs.getString("text")) }, id)
  34. @Service class MessageService(val db: JdbcTemplate) { fun findMessages(): List<Message> =

    db.query("select * from messages") { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun findMessageById(id: String): List<Message> = db.query("select * from messages where id = ?", id) { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun post(message: Message){ db.update("insert into messages values ( ?, ? )", message.id ?: message.text.uuid(), message.text) } } fun findMessageById(id: String): List<Message> = db.query("select * from messages where id = ?", RowMapper { rs, _ -> Message(rs.getString("id"), rs.getString("text")) }, id)
  35. @Service class MessageService(val db: JdbcTemplate) { fun findMessages(): List<Message> =

    db.query("select * from messages") { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun findMessageById(id: String): List<Message> = db.query("select * from messages where id = ?", id) { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun post(message: Message){ db.update("insert into messages values ( ?, ? )", message.id ?: message.text.uuid(), message.text) } } fun findMessageById(id: String): List<Message> = db.query("select * from messages where id = ?", RowMapper { rs, _ -> Message(rs.getString("id"), rs.getString("text")) }, id)
  36. @Service class MessageService(val db: JdbcTemplate) { fun findMessages(): List<Message> =

    db.query("select * from messages") { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun findMessageById(id: String): List<Message> = db.query("select * from messages where id = ?", id) { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun post(message: Message){ db.update("insert into messages values ( ?, ? )", message.id ?: message.text.uuid(), message.text) } } fun findMessageById(id: String): List<Message> = db.query("select * from messages where id = ?", RowMapper { rs, _ -> Message(rs.getString("id"), rs.getString("text")) }, id) RowMapper is a functional interface
  37. @Service class MessageService(val db: JdbcTemplate) { fun findMessages(): List<Message> =

    db.query("select * from messages") { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun findMessageById(id: String): List<Message> = db.query("select * from messages where id = ?", id) { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun post(message: Message){ db.update("insert into messages values ( ?, ? )", message.id ?: message.text.uuid(), message.text) } } fun findMessageById(id: String): List<Message> = db.query("select * from messages where id = ?", { rs, _ -> Message(rs.getString("id"), rs.getString("text")) }, id) “SAM conversion” is performed by the compiler
  38. @Service class MessageService(val db: JdbcTemplate) { fun findMessages(): List<Message> =

    db.query("select * from messages") { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun findMessageById(id: String): List<Message> = db.query("select * from messages where id = ?", id) { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun post(message: Message){ db.update("insert into messages values ( ?, ? )", message.id ?: message.text.uuid(), message.text) } } fun findMessageById(id: String): List<Message> = db.query("select * from messages where id = ?", { rs, _ -> Message(rs.getString("id"), rs.getString("text")) }, id)
  39. @Service class MessageService(val db: JdbcTemplate) { fun findMessages(): List<Message> =

    db.query("select * from messages") { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun findMessageById(id: String): List<Message> = db.query("select * from messages where id = ?", id) { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun post(message: Message){ db.update("insert into messages values ( ?, ? )", message.id ?: message.text.uuid(), message.text) } } fun findMessageById(id: String): List<Message> = db.query("select * from messages where id = ?", id, { rs, _ -> Message(rs.getString("id"), rs.getString("text")) })
  40. @Service class MessageService(val db: JdbcTemplate) { fun findMessages(): List<Message> =

    db.query("select * from messages") { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun findMessageById(id: String): List<Message> = db.query("select * from messages where id = ?", id) { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun post(message: Message){ db.update("insert into messages values ( ?, ? )", message.id ?: message.text.uuid(), message.text) } } fun findMessageById(id: String): List<Message> = db.query("select * from messages where id = ?", id, { rs, _ -> Message(rs.getString("id"), rs.getString("text")) }) vararg is not at the last position
  41. @Service class MessageService(val db: JdbcTemplate) { fun findMessages(): List<Message> =

    db.query("select * from messages") { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun findMessageById(id: String): List<Message> = db.query("select * from messages where id = ?", id) { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun post(message: Message){ db.update("insert into messages values ( ?, ? )", message.id ?: message.text.uuid(), message.text) } } fun findMessageById(id: String): List<Message> = db.query("select * from messages where id = ?", id, { rs, _ -> Message(rs.getString("id"), rs.getString("text")) }) Lambda expression is at the last position
  42. @Service class MessageService(val db: JdbcTemplate) { fun findMessages(): List<Message> =

    db.query("select * from messages") { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun findMessageById(id: String): List<Message> = db.query("select * from messages where id = ?", id) { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun post(message: Message){ db.update("insert into messages values ( ?, ? )", message.id ?: message.text.uuid(), message.text) } }
  43. @Service class MessageService(val db: JdbcTemplate) { fun findMessages(): List<Message> =

    db.query("select * from messages") { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun findMessageById(id: String): List<Message> = db.query("select * from messages where id = ?", id) { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun post(message: Message){ db.update("insert into messages values ( ?, ? )", message.id ?: message.text.uuid(), message.text) } } Passing trailing lambda as a parameter to the function
  44. @Service class MessageService(val db: JdbcTemplate) { fun findMessages(): List<Message> =

    db.query("select * from messages") { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun findMessageById(id: String): List<Message> = db.query("select * from messages where id = ?", id) { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun post(message: Message){ db.update("insert into messages values ( ?, ? )", message.id ?: message.text.uuid(), message.text) } }
  45. @Service class MessageService(val db: JdbcTemplate) { fun findMessages(): List<Message> =

    db.query("select * from messages") { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun findMessageById(id: String): List<Message> = db.query("select * from messages where id = ?", id) { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun post(message: Message){ db.update("insert into messages values ( ?, ? )", message.id ?: message.text.uuid(), message.text) } } This is an extension function For the existing Java class in the Spring framework
  46. @Service class MessageService(val db: JdbcTemplate) { fun findMessages(): List<Message> =

    db.query("select * from messages") { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun findMessageById(id: String): List<Message> = db.query("select * from messages where id = ?", id) { rs, _ - > Message(rs.getString("id"), rs.getString("text")) } fun post(message: Message){ db.update("insert into messages values ( ?, ? )", message.id ?: message.text.uuid(), message.text) } }
  47. extensions & Trailing lambdas

  48. fun String.uuid() = UUID.nameUUIDFromBytes(encodeToByteArray()).toString()

  49. fun String.uuid() = UUID.nameUUIDFromBytes(encodeToByteArray()).toString() fun applyAction(vararg s: String, action: (String)

    -> Unit) { s.forEach(action) }
  50. fun String.uuid() = UUID.nameUUIDFromBytes(encodeToByteArray()).toString() fun applyAction(vararg s: String, action: (String)

    -> Unit) { s.forEach(action) } fun main() { applyAction("hello", "bye") { s: String -> println(s.uuid()) } }
  51. fun String.uuid() = UUID.nameUUIDFromBytes(encodeToByteArray()).toString() fun applyAction(vararg s: String, action: (String)

    -> Unit) { s.forEach(action) } fun main() { applyAction("hello", "bye") { s: String -> println(s.uuid()) } } Note to self: open the IDE
  52. more kotlin extensions In spring

  53. class HtmlController(val messageService: MessageService) { @GetMapping("/") fun index(model: Model): String

    { val messages = messageService.latest() model.asMap()["messages"] = messages model.asMap()["lastMessageId"] = messages.lastOrNull() ? . id ? : "" return "chat" } }
  54. class HtmlController(val messageService: MessageService) { @GetMapping("/") fun index(model: Model): String

    { val messages = messageService.latest() model.asMap()["messages"] = messages model.asMap()["lastMessageId"] = messages.lastOrNull() ? . id ? : "" return "chat" } }
  55. class HtmlController(val messageService: MessageService) { @GetMapping("/") fun index(model: Model): String

    { val messages = messageService.latest() model.asMap()["messages"] = messages model.asMap()["lastMessageId"] = messages.lastOrNull() ? . id ? : "" return "chat" } }
  56. class HtmlController(val messageService: MessageService) { @GetMapping("/") fun index(model: Model): String

    { val messages = messageService.latest() model["messages"] = messages model["lastMessageId"] = messages.lastOrNull() ?. id ?: "" return "chat" } }
  57. Type-safe builders A.k.a. dsl

  58. @SpringBootApplication class DemoApplication fun main(args: Array<String>) { runApplication<DemoApplication>(*args) { addInitializers(beans)

    } }
  59. @SpringBootApplication class DemoApplication fun main(args: Array<String>) { runApplication<DemoApplication>(*args) { this.addInitializers(beans)

    } } “this” refers to SpringApplication instance
  60. val beans = beans { bean { CommandLineRunner { println("start

    data initialization .. . ") val repository = ref<MessageRepository>() repository.save(Message(text = "this is the first message!")) repository.save(Message(text = "this is the second } } } @SpringBootApplication class DemoApplication fun main(args: Array<String>) { runApplication<DemoApplication>(*args) { this.addInitializers(beans) } }
  61. @SpringBootApplication class DemoApplication fun main(args: Array<String>) { runApplication<DemoApplication>(*args) { this.addInitializers(beans)

    } } val beans = beans { bean { CommandLineRunner { println("start data initialization .. . ") val repository = ref<MessageRepository>() repository.save(Message(text = "this is the first message!")) repository.save(Message(text = "this is the second } } }
  62. Trailing lambdas + (extension) functions + named parameters + lambda

    w/ receiver = DSL fun foo(f: () -> Unit) {} fun bar(f: () -> Unit) {} fun baz(f: () -> Unit) {} fun blah(id: Int, f: Klazz.() -> Unit) {} class Klazz(var text: String) fun Klazz.hello() : String = "Hello"
  63. Null safety

  64. None
  65. val person: Person? = getPerson() println(person.firstName) fun getPerson(): Person? {…}

    data class Person( var firstName: String, // … )
  66. val person: Person? = getPerson() println(person.firstName) fun getPerson(): Person? {…}

    data class Person( var firstName: String, // … ) Can return null
  67. val person: Person? = getPerson() println(person.firstName) fun getPerson(): Person? {…}

    data class Person( var firstName: String, // … ) Can be null
  68. val person: Person? = getPerson() println(person ?. firstName) fun getPerson():

    Person? {…} data class Person( var firstName: String, // … ) Null-safe dereferencing
  69. val person: Person? = getPerson() println(person !! .firstName) fun getPerson():

    Person? {…} data class Person( var firstName: String, // … ) “I know what I’m doing”
  70. None
  71. None
  72. Can be null Should not be null

  73. None
  74. None
  75. None
  76. None
  77. build.gradle.kts

  78. Kotlin features you have seen now Type inference Inline functions

    Data classes Rei fi ed generics Top-level functions Named arguments Operator overloading Trailing lambdas Extension functions Functional literal with receiver Nullable types Support for JSR-305
  79. Kuestions? speakerdeck.com/antonarhipov @antonarhipov