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. 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.
  2. 13  Status Reference documentation in Kotlin Spring Boot 2.2

    Next year estimate Spring Framework Spring Boot Spring Data Spring Security
  3. 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
  4. 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
  5. 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
  6. 22  router { GET("/hello", ::hello) } fun hello(request: ServerRequest)

    = ServerResponse.ok().body("Hello world!") Router
  7. 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
  8. 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
  9. 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
  10. 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
  11. 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
  12. Coroutines allows to consume Spring Reactive stack with a nice

    balance between imperative and declarative style 32
  13. Note: suspending functions color your API* 38 * As explained

    by Bob Nystrom in its great blog post What Color is Your Function?
  14. 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
  15. 45  interface Flow<T> { suspend fun collect(collector: FlowCollector<T>) }

    interface FlowCollector<T> { suspend fun emit(value: T) } Flow API
  16. 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
  17. 47  val flow = flow { for (i in

    1..3) { delay(100) emit(i) } } Create a Flow
  18. 53  @GetMapping("/api/banner") suspend fun suspendingEndpoint(): Banner { delay(10) return

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

    model["banner"] = Banner("title", "Lorem ipsum") return "index" } WebFlux suspending view rendering
  20. 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
  21. 57  coRouter { GET("/hello", ::hello) } suspend fun hello(request:

    ServerRequest) = ServerResponse.ok().bodyValueAndAwait("Hello world!") WebFlux Router
  22. 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
  23. 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
  24. 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
  25. 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
  26. 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
  27. 65  @ConfigurationProperties("blog") class BlogProperties { lateinit var title: String

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

    banner: Banner) { data class Banner(val title: String?, val content: String) } @ConfigurationProperties + @ConstructorBinding
  29. 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