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

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

    View Slide

  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.

    View Slide

  3. 3

    Less flights

    View Slide

  4. Why Kotlin?

    View Slide

  5. 5

    Improve signal to noise ratio

    View Slide

  6. 6

    Improve the signal to noise ratio
    of the code
    Safety

    View Slide

  7. 7

    Improve the signal to noise ratio
    of the code
    Discoverability

    View Slide

  8. 8

    Pleasure

    View Slide

  9. 9

    Android

    View Slide

  10. Spring Kotlin

    View Slide

  11. How much?

    View Slide

  12. Framework documentation in Kotlin!
    12

    View Slide

  13. 13

    Status
    Reference documentation in Kotlin Spring Boot 2.2 Next year estimate
    Spring Framework
    Spring Boot
    Spring Data
    Spring Security

    View Slide

  14. Gradle Kotlin DSL on start.spring.io
    14

    View Slide

  15. More DSLs

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  19. Spring MVC DSL and functional API

    View Slide

  20. 20

    fun hello(request: ServerRequest): ServerResponse
    Handler API

    View Slide

  21. 21

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

    View Slide

  22. 22

    router {
    GET("/hello", ::hello)
    }
    fun hello(request: ServerRequest) =
    ServerResponse.ok().body("Hello world!")
    Router

    View Slide

  23. 23

    @Configuration
    class RoutesConfiguration {
    @Bean
    fun routes(): RouterFunction = router {
    GET("/hello", ::hello)
    }
    fun hello(request: ServerRequest) =
    ServerResponse.ok().body("Hello world!")
    }
    Expose the router to Spring Boot

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  28. Coroutines

    View Slide

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

    View Slide

  30. The foundations are in Kotlin language
    30

    View Slide

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

    View Slide

  32. Coroutines allows to consume Spring Reactive
    stack with a nice balance between imperative
    and declarative style
    32

    View Slide

  33. Operations are sequential by default
    33

    View Slide

  34. Concurrency is explicit
    34

    View Slide

  35. 3 main building blocks you need to know
    35

    View Slide

  36. Suspending function
    36

    View Slide

  37. 37

    suspend fun hello() {
    delay(1000)
    println("Hello world!")
    }
    Suspending functions

    View Slide

  38. Note: suspending functions color your API*
    38
    * As explained by Bob Nystrom in its great blog post What Color is Your Function?

    View Slide

  39. Structured concurrency
    39

    View Slide

  40. 40

    suspend fun loadAndCombine(name1: String, name2: String) = coroutineScope {
    val deferred1: Deferred = async { loadImage(name1) }
    val deferred2: Deferred = async { loadImage(name2) }
    combineImages(deferred1.await(), deferred2.await())
    }
    Structured concurrency

    View Slide

  41. Flow*
    41
    * kotlinx.coroutines.flow.Flow not java.util.concurrent.Flow

    View Slide

  42. Coroutines 1.3 introduces a new Reactive type:
    Flow
    42

    View Slide

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

    View Slide

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

    View Slide

  45. 45

    interface Flow {
    suspend fun collect(collector: FlowCollector)
    }
    interface FlowCollector {
    suspend fun emit(value: T)
    }
    Flow API

    View Slide

  46. 46

    fun Flow.filter(predicate: suspend (T) -> Boolean): Flow =
    transform { value ->
    if (predicate(value)) return@transform emit(value)
    }
    Operators as extensions easy to implement

    View Slide

  47. 47

    val flow = flow {
    for (i in 1..3) {
    delay(100)
    emit(i)
    }
    }
    Create a Flow

    View Slide

  48. 48

    flow.filter { it < 2 }.map(::asyncOperation).collect()
    Consume a Flow

    View Slide

  49. What about Spring support for
    Coroutines?

    View Slide

  50. Spring provides official Coroutines support for
    Spring WebFlux, Data*, Vault, RSocket
    50
    * Redis, MongoDB, Cassandra, R2DBC

    View Slide

  51. No new types introduced
    51

    View Slide

  52. Seamless support with the annotation
    programming model
    52

    View Slide

  53. 53

    @GetMapping("/api/banner")
    suspend fun suspendingEndpoint(): Banner {
    delay(10)
    return Banner("title", "Lorem ipsum")
    }
    WebFlux suspending handler method

    View Slide

  54. 54

    @GetMapping("/banner")
    suspend fun render(model: Model): String {
    delay(10)
    model["banner"] = Banner("title", "Lorem ipsum")
    return "index"
    }
    WebFlux suspending view rendering

    View Slide

  55. Reactive types extensions for Coroutines APIs
    55

    View Slide

  56. 56

    @GetMapping("/banners")
    suspend fun flow(): Flow = client.get()
    .uri("/messages")
    .accept(MediaType.TEXT_EVENT_STREAM)
    .retrieve()
    .bodyToFlow()
    .map { Banner("title", it) }
    WebClient

    View Slide

  57. 57

    coRouter {
    GET("/hello", ::hello)
    }
    suspend fun hello(request: ServerRequest) =
    ServerResponse.ok().bodyValueAndAwait("Hello world!")
    WebFlux Router

    View Slide

  58. 58

    private val requester: RSocketRequester = ...
    @MessageMapping("locate.radars.within")
    fun findRadars(request: MapRequest): Flow = requester
    .route("locate.radars.within")
    .data(request.viewBox)
    .retrieveFlow()
    .take(request.maxRadars)
    RSocket

    View Slide

  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().table("users").using(user).await()
    }
    }
    Spring Data R2DBC with transactions

    View Slide

  60. 60

    class UserRepository(private val mongo: ReactiveFluentMongoOperations) {
    fun findAll(): Flow =
    mongo.query().flow()
    suspend fun findOne(id: String): User = mongo.query()
    .matching(query(where("id").isEqualTo(id))).awaitOne()
    suspend fun insert(user: User): User =
    mongo.insert().oneAndAwait(user)
    suspend fun update(user: User): User =
    mongo.update().replaceWith(user)
    .asType().findReplaceAndAwait()
    }
    Spring Data MongoDB

    View Slide

  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

    View Slide

  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

    View Slide

  63. https://github.com/sdeleuze/spring-boot-coroutines-demo
    63

    View Slide

  64. Spring Boot

    View Slide

  65. 65

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

    View Slide

  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

    View Slide

  67. 67

    start.spring.io helps to share Kotlin projects

    View Slide

  68. Kofu: the mother of all DSLs

    View Slide

  69. application { }
    69

    View Slide

  70. Explicit configuration for Spring Boot using Kotlin DSLs
    70

    View Slide

  71. Experimental
    71

    View Slide

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

    View Slide

  73. 73

    val app = application(WebApplicationType.SERVLET) {
    beans {
    bean()
    bean()
    }
    webMvc {
    port = if (profiles.contains("test")) 8181 else 8080
    router {
    val handler = ref()
    GET("/", handler::hello)
    GET("/api", handler::json)
    }
    converters {
    string()
    jackson()
    }
    }
    }
    Spring Boot configured with Kofu DSL

    View Slide

  74. Composable configurations
    74

    View Slide

  75. Discoverability via auto-complete instead of conventions
    75

    View Slide

  76. Faster startup, less memory consumption
    76

    View Slide

  77. 77

    Faster startup

    View Slide

  78. Demo

    View Slide

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

    View Slide

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

    View Slide

  81. GraalVM Native

    View Slide

  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

    View Slide

  83. Thanks
    Twitter: @sdeleuze
    Credits to ibrandify and flaticon for some icons used

    View Slide