Slide 1

Slide 1 text

The state of Kotlin support in Spring Sébastien Deleuze October 19, 2019

Slide 2

Slide 2 text

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.

Slide 3

Slide 3 text

3  Less flights

Slide 4

Slide 4 text

Why Kotlin?

Slide 5

Slide 5 text

5  Improve signal to noise ratio

Slide 6

Slide 6 text

6  Improve the signal to noise ratio of the code Safety

Slide 7

Slide 7 text

7  Improve the signal to noise ratio of the code Discoverability

Slide 8

Slide 8 text

8  Pleasure

Slide 9

Slide 9 text

9  Android

Slide 10

Slide 10 text

Spring Kotlin

Slide 11

Slide 11 text

How much?

Slide 12

Slide 12 text

Framework documentation in Kotlin! 12 

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

Gradle Kotlin DSL on start.spring.io 14 

Slide 15

Slide 15 text

More DSLs

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

Spring MVC DSL and functional API

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

Coroutines

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

The foundations are in Kotlin language 30

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

Operations are sequential by default 33

Slide 34

Slide 34 text

Concurrency is explicit 34

Slide 35

Slide 35 text

3 main building blocks you need to know 35

Slide 36

Slide 36 text

Suspending function 36

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

Structured concurrency 39

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

Coroutines 1.3 introduces a new Reactive type: Flow 42

Slide 43

Slide 43 text

Flow is the equivalent of Flux in Coroutines world 43

Slide 44

Slide 44 text

Flow is interoperable with Reactive Streams and supports backpressure 44

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

What about Spring support for Coroutines?

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

No new types introduced 51

Slide 52

Slide 52 text

Seamless support with the annotation programming model 52

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

Reactive types extensions for Coroutines APIs 55

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

Spring Boot

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

66  @ConstructorBinding @ConfigurationProperties("blog") data class BlogProperties(val title: String, val banner: Banner) { data class Banner(val title: String?, val content: String) } @ConfigurationProperties + @ConstructorBinding

Slide 67

Slide 67 text

67  start.spring.io helps to share Kotlin projects

Slide 68

Slide 68 text

Kofu: the mother of all DSLs

Slide 69

Slide 69 text

application { } 69

Slide 70

Slide 70 text

Explicit configuration for Spring Boot using Kotlin DSLs 70

Slide 71

Slide 71 text

Experimental 71

Slide 72

Slide 72 text

Leverage existing DSLs : beans, router, security 7 2

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

Composable configurations 74

Slide 75

Slide 75 text

Discoverability via auto-complete instead of conventions 75

Slide 76

Slide 76 text

Faster startup, less memory consumption 76

Slide 77

Slide 77 text

77  Faster startup

Slide 78

Slide 78 text

Demo

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

GraalVM Native

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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