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

The state of Kotlin support in Spring

The state of Kotlin support in Spring

Sébastien Deleuze

October 19, 2019
Tweet

More Decks by Sébastien Deleuze

Other Decks in Programming

Transcript

  1. The state of Kotlin support in Spring Sébastien Deleuze October

    19, 2019
  2. Safe Harbor Statement The following is intended to outline the

    general direction of Pivotal's offerings. It is intended for information purposes only and may not be incorporated into any contract. Any information regarding pre-release of Pivotal offerings, future updates or other planned modifications is subject to ongoing evaluation by Pivotal and is subject to change. This information is provided without warranty or any kind, express or implied, and is not a commitment to deliver any material, code, or functionality, and should not be relied upon in making purchasing decisions regarding Pivotal's offerings. These purchasing decisions should only be based on features currently available. The development, release, and timing of any features or functionality described for Pivotal's offerings in this presentation remain at the sole discretion of Pivotal. Pivotal has no obligation to update forward looking information in this presentation.
  3. 3  Less flights

  4. Why Kotlin?

  5. 5  Improve signal to noise ratio

  6. 6  Improve the signal to noise ratio of the

    code Safety
  7. 7  Improve the signal to noise ratio of the

    code Discoverability
  8. 8  Pleasure

  9. 9  Android

  10. Spring Kotlin

  11. How much?

  12. Framework documentation in Kotlin! 12 

  13. 13  Status Reference documentation in Kotlin Spring Boot 2.2

    Next year estimate Spring Framework Spring Boot Spring Data Spring Security
  14. Gradle Kotlin DSL on start.spring.io 14 

  15. More DSLs

  16. 16  mockMvc.request(HttpMethod.GET, "/person/{name}", "Lee") { secure = true accept

    = APPLICATION_JSON headers { contentLanguage = Locale.FRANCE } principal = Principal { "foo" } }.andExpect { status { isOk } content { contentType(APPLICATION_JSON) } jsonPath("$.name") { value("Lee") } content { json("""{"someBoolean": false}""", false) } }.andDo { print() } MockMvc DSL by Clint Checketts and JB Nizet
  17. 17 contract { request { url = url("/foo") method =

    PUT headers { header("foo", "bar") } body = body("foo" to "bar") } response { status = OK } } Spring Cloud Contract DSL by Tim Ysewyn
  18. 18  http { formLogin { loginPage = "/log-in" }

    authorizeRequests { authorize("/css/**", permitAll) authorize("/user/**", hasAuthority("ROLE_USER")) } } Spring Security DSL by Eleftheria Stein https://github.com/spring-projects-experimental/spring-security-kotlin-dsl
  19. Spring MVC DSL and functional API

  20. 20  fun hello(request: ServerRequest): ServerResponse Handler API

  21. 21  fun hello(request: ServerRequest) = ServerResponse.ok().body("Hello world!") Handler implementation

  22. 22  router { GET("/hello", ::hello) } fun hello(request: ServerRequest)

    = ServerResponse.ok().body("Hello world!") Router
  23. 23  @Configuration class RoutesConfiguration { @Bean fun routes(): RouterFunction<ServerResponse>

    = router { GET("/hello", ::hello) } fun hello(request: ServerRequest) = ServerResponse.ok().body("Hello world!") } Expose the router to Spring Boot
  24. 24  @Component class PersonHandler(private val repository: PersonRepository) { fun

    listPeople(request: ServerRequest): ServerResponse { // ... } fun createPerson(request: ServerRequest): ServerResponse { // ... } fun getPerson(request: ServerRequest): ServerResponse { // ... } } A more realistic handler
  25. 25  @Configuration class RouteConfiguration { @Bean fun routes(handler: PersonHandler)

    = router { accept(APPLICATION_JSON).nest { GET("/person/{id}", handler::getPerson) GET("/person", handler::listPeople) } POST("/person", handler::createPerson) } } A more realistic router
  26. 26  @Configuration class RouteConfiguration { @Bean fun routes(routeRepository: RouteRepository)

    = router { for (route in routeRepository.listRoutes()) { GET("/$route") { request -> hello(request, route) } } } fun hello(request: ServerRequest, message: String) = ServerResponse.ok().body("Hello $message!") } You can even create routes dynamically
  27. 27  Status Kotlin DSLs Spring Boot 2.1 Spring Boot

    2.2 Next year estimate Beans DSL WebFlux router DSL WebFlux Coroutines router DSL WebMvc router DSL MockMvc DSL Spring Security DSL
  28. Coroutines

  29. Coroutines are lightweight threads (you can create 100.000s of them)

    29
  30. The foundations are in Kotlin language 30

  31. But most of the implementation lives on library side in

    kotlinx.coroutines 31
  32. Coroutines allows to consume Spring Reactive stack with a nice

    balance between imperative and declarative style 32
  33. Operations are sequential by default 33

  34. Concurrency is explicit 34

  35. 3 main building blocks you need to know 35

  36. Suspending function 36

  37. 37  suspend fun hello() { delay(1000) println("Hello world!") }

    Suspending functions
  38. Note: suspending functions color your API* 38 * As explained

    by Bob Nystrom in its great blog post What Color is Your Function?
  39. Structured concurrency 39

  40. 40  suspend fun loadAndCombine(name1: String, name2: String) = coroutineScope

    { val deferred1: Deferred<Image> = async { loadImage(name1) } val deferred2: Deferred<Image> = async { loadImage(name2) } combineImages(deferred1.await(), deferred2.await()) } Structured concurrency
  41. Flow<T>* 41 * kotlinx.coroutines.flow.Flow not java.util.concurrent.Flow

  42. Coroutines 1.3 introduces a new Reactive type: Flow<T> 42

  43. Flow is the equivalent of Flux in Coroutines world 43

  44. Flow is interoperable with Reactive Streams and supports backpressure 44

  45. 45  interface Flow<T> { suspend fun collect(collector: FlowCollector<T>) }

    interface FlowCollector<T> { suspend fun emit(value: T) } Flow API
  46. 46  fun <T> Flow<T>.filter(predicate: suspend (T) -> Boolean): Flow<T>

    = transform { value -> if (predicate(value)) return@transform emit(value) } Operators as extensions easy to implement
  47. 47  val flow = flow { for (i in

    1..3) { delay(100) emit(i) } } Create a Flow
  48. 48  flow.filter { it < 2 }.map(::asyncOperation).collect() Consume a

    Flow
  49. What about Spring support for Coroutines?

  50. Spring provides official Coroutines support for Spring WebFlux, Data*, Vault,

    RSocket 50 * Redis, MongoDB, Cassandra, R2DBC
  51. No new types introduced 51

  52. Seamless support with the annotation programming model 52

  53. 53  @GetMapping("/api/banner") suspend fun suspendingEndpoint(): Banner { delay(10) return

    Banner("title", "Lorem ipsum") } WebFlux suspending handler method
  54. 54  @GetMapping("/banner") suspend fun render(model: Model): String { delay(10)

    model["banner"] = Banner("title", "Lorem ipsum") return "index" } WebFlux suspending view rendering
  55. Reactive types extensions for Coroutines APIs 55

  56. 56  @GetMapping("/banners") suspend fun flow(): Flow<Banner> = client.get() .uri("/messages")

    .accept(MediaType.TEXT_EVENT_STREAM) .retrieve() .bodyToFlow<String>() .map { Banner("title", it) } WebClient
  57. 57  coRouter { GET("/hello", ::hello) } suspend fun hello(request:

    ServerRequest) = ServerResponse.ok().bodyValueAndAwait("Hello world!") WebFlux Router
  58. 58  private val requester: RSocketRequester = ... @MessageMapping("locate.radars.within") fun

    findRadars(request: MapRequest): Flow<Radar> = requester .route("locate.radars.within") .data(request.viewBox) .retrieveFlow<AirportLocation>() .take(request.maxRadars) RSocket
  59. 59  class PersonRepository(private val client: DatabaseClient, private val operator:

    TransactionalOperator) { suspend fun initDatabase() = operator.executeAndAwait { save(User("smaldini", "Stéphane", "Maldini")) save(User("sdeleuze", "Sébastien", "Deleuze")) save(User("bclozel", "Brian", "Clozel")) } suspend fun save(user: User) { client.insert().into<User>().table("users").using(user).await() } } Spring Data R2DBC with transactions
  60. 60  class UserRepository(private val mongo: ReactiveFluentMongoOperations) { fun findAll():

    Flow<User> = mongo.query<User>().flow() suspend fun findOne(id: String): User = mongo.query<User>() .matching(query(where("id").isEqualTo(id))).awaitOne() suspend fun insert(user: User): User = mongo.insert<User>().oneAndAwait(user) suspend fun update(user: User): User = mongo.update<User>().replaceWith(user) .asType<User>().findReplaceAndAwait() } Spring Data MongoDB
  61. 61  Coroutines are now the default way to go

    Reactive in Kotlin Coroutines dependency added by default on start.spring.io WebFlux & RSocket Kotlin code samples provided with Coroutines API
  62. 62  Status Coroutines support Spring Boot 2.2 Next year

    estimate Spring WebFlux functional APIs Spring WebFlux WebClient Spring WebFlux @RequestMapping RSocket @MessageMapping Spring Data Reactive APIs Functional transactions Spring MVC @RequestMapping Spring Data repositories (DATACMNS-1508) @Transactional
  63. https://github.com/sdeleuze/spring-boot-coroutines-demo 63

  64. Spring Boot

  65. 65  @ConfigurationProperties("blog") class BlogProperties { lateinit var title: String

    val banner = Banner() class Banner { var title: String? = null lateinit var content: String } } @ConfigurationProperties
  66. 66  @ConstructorBinding @ConfigurationProperties("blog") data class BlogProperties(val title: String, val

    banner: Banner) { data class Banner(val title: String?, val content: String) } @ConfigurationProperties + @ConstructorBinding
  67. 67  start.spring.io helps to share Kotlin projects

  68. Kofu: the mother of all DSLs

  69. application { } 69

  70. Explicit configuration for Spring Boot using Kotlin DSLs 70

  71. Experimental 71

  72. Leverage existing DSLs : beans, router, security 7 2

  73. 73  val app = application(WebApplicationType.SERVLET) { beans { bean<SampleService>()

    bean<SampleHandler>() } webMvc { port = if (profiles.contains("test")) 8181 else 8080 router { val handler = ref<SampleHandler>() GET("/", handler::hello) GET("/api", handler::json) } converters { string() jackson() } } } Spring Boot configured with Kofu DSL
  74. Composable configurations 74

  75. Discoverability via auto-complete instead of conventions 75

  76. Faster startup, less memory consumption 76

  77. 77  Faster startup

  78. Demo

  79. https://github.com/spring-projects-experimental/spring-fu 79

  80. My hope is to make Kofu the Spring Boot Kotlin

    DSL 80
  81. GraalVM Native

  82. https://github.com/spring-projects-experimental/spring-graal-native Kotlin with Spring MVC or Spring WebFlux works on

    GraalVM native Coroutines support is broken due to graal#366 82
  83. Thanks Twitter: @sdeleuze Credits to ibrandify and flaticon for some

    icons used