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

Why Spring loves Kotlin

Why Spring loves Kotlin

Slides of my Devoxx 2017 & SpringOne Platform talk, video available at https://www.youtube.com/watch?v=kbMXAjWEft0. Let's discovers Spring official Kotlin support with a journey from Spring Boot 1 with Java to Spring Boot 2 with Kotlin.

Sébastien Deleuze

November 08, 2017
Tweet

More Decks by Sébastien Deleuze

Other Decks in Programming

Transcript

  1. Why Spring Kotlin
    Sébastien Deleuze
    @sdeleuze

    View Slide

  2. 2
    Most popular way to build web applications
    +
    Spring
    Boot

    View Slide

  3. 3
    Let’s see how far we can go with ...
    Kotlin
    +
    Spring
    Boot

    View Slide

  4. 4

    View Slide

  5. 5
    Migrating a typical Boot application to Kotlin
    https://github.com/sdeleuze/spring-kotlin-deepdive

    View Slide

  6. 6
    Step 1
    Kotlin

    View Slide

  7. 7
    Step 2
    Spring Boot 1 Spring Boot 2
    based on Spring Framework 5

    View Slide

  8. 8
    Step 3
    Spring MVC Spring WebFlux

    View Slide

  9. 9
    Step 4
    Spring WebFlux
    Functional API & Kotlin DSL
    Spring WebFlux
    @nnotations

    View Slide

  10. From Java to Kotlin
    Step 1 Kotlin
    Step 2 Boot 2
    Step 3 WebFlux
    Step 4 Kotlin DSL & Functional API

    View Slide

  11. 11
    https://start.spring.io/#!language=kotlin

    View Slide

  12. 12
    Domain model
    @Document
    data class Article(
    @Id val slug: String,
    val title: String,
    val headline: String,
    val content: String,
    @DBRef val author: User,
    val addedAt: LocalDateTime = now())
    @Document
    data class User(
    @Id val login: String,
    val firstname: String,
    val lastname: String,
    val description: String? = null)
    @Document
    public class Article {
    @Id
    private String slug;
    private String title;
    private LocalDateTime addedAt;
    private String headline;
    private String content;
    @DBRef
    private User author;
    public Article() {
    }
    public Article(String slug, String title, String
    headline, String content, User author) {
    this(slug, title, headline, content, author,
    LocalDateTime.now());
    }
    public Article(String slug, String title, String
    headline, String content, User author, LocalDateTime
    addedAt) {
    this.slug = slug;
    this.title = title;
    this.addedAt = addedAt;
    this.headline = headline;
    this.content = content;
    this.author = author;
    }
    public String getSlug() {
    return slug;
    }
    public void setSlug(String slug) {
    this.slug = slug;
    }
    public String getTitle() {
    return title;
    }

    View Slide

  13. 13
    Expressive test function names with backticks
    class EmojTests {
    @Test
    fun `Why Spring ❤ Kotlin?`() {
    println("Because I can use emoj in function names \uD83D\uDE09")
    }
    }
    > Because I can use emoj in function names

    View Slide

  14. 14
    @RestController
    public class UserController {
    private final UserRepository userRepository;
    public UserController(UserRepository userRepository) {
    this.userRepository = userRepository;
    }
    @GetMapping("/user/{login}")
    public User findOne(@PathVariable String login) {
    return userRepository.findOne(login);
    }
    @GetMapping("/user")
    public Iterable findAll() {
    return userRepository.findAll();
    }
    @PostMapping("/user")
    public User save(@RequestBody User user) {
    return userRepository.save(user);
    }
    }
    Spring MVC controller written in Java

    View Slide

  15. 15
    @RestController
    class UserController(private val repo: UserRepository) {
    @GetMapping("/user/{id}")
    fun findOne(@PathVariable id: String) = repo.findOne(id)
    @GetMapping("/user")
    fun findAll() = repo.findAll()
    @PostMapping("/user")
    fun save(@RequestBody user: User) = repo.save(user)
    }
    Spring MVC controller written in Kotlin

    View Slide

  16. 16
    Inferred type hints in IDEA
    Settings
    Editor
    General
    Appearance
    Show parameter name hints
    Select Kotlin
    Check “Show function/property/local value return type hints”

    View Slide

  17. kotlin-spring compiler plugin
    Automatically open Spring annotated classes and methods
    @SpringBootApplication
    open class Application {
    @Bean
    open fun foo() = Foo()
    @Bean
    open fun bar(foo: Foo) = Bar(foo)
    }
    @SpringBootApplication
    class Application {
    @Bean
    fun foo() = Foo()
    @Bean
    fun bar(foo: Foo) = Bar(foo)
    }
    Without kotlin-spring plugin With kotlin-spring plugin
    17

    View Slide

  18. From Java to Kotlin
    Step 1 Kotlin
    Step 2 Boot 2
    Step 3 WebFlux
    Step 4 Kotlin DSL & Functional API

    View Slide

  19. Spring Kotlin
    and officially supports it
    Spring Framework 5
    Spring Boot 2.0 (M7 available, GA early 2018)
    Reactor Core 3.1
    Spring Data Kay

    View Slide

  20. 20
    Kotlin support out of the box

    View Slide

  21. 21
    Kotlin support documentation
    https://goo.gl/uwyjQn

    View Slide

  22. Running Spring Boot 1 application with Kotlin
    22
    @SpringBootApplication
    class Application
    fun main(args: Array) {
    SpringApplication.run(Application::class.java, *args)
    }

    View Slide

  23. Running Spring Boot 2 application with Kotlin
    23
    @SpringBootApplication
    class Application
    fun main(args: Array) {
    runApplication(*args)
    }

    View Slide

  24. Declaring additional beans
    24
    @SpringBootApplication
    class Application {
    @Bean
    fun foo() = Foo()
    @Bean
    fun bar(foo: Foo) = Bar(foo)
    }
    fun main(args: Array) {
    runApplication(*args)
    }

    View Slide

  25. Customizing SpringApplication
    25
    @SpringBootApplication
    class Application {
    @Bean
    fun foo() = Foo()
    @Bean
    fun bar(foo: Foo) = Bar(foo)
    }
    fun main(args: Array) {
    runApplication(*args) {
    setBannerMode(OFF)
    }
    }

    View Slide

  26. Array-like Kotlin extension for Model
    operator fun Model.set(attributeName: String, attributeValue: Any) {
    this.addAttribute(attributeName, attributeValue)
    }
    @GetMapping("/")
    public String blog(Model model) {
    model.addAttribute("title", "Blog");
    model.addAttribute("articles", repository.findAll());
    return "blog";
    }
    @GetMapping("/")
    fun blog(model: Model): String {
    model["title"] = "Blog"
    model["articles"] = repository.findAll()
    return "blog"
    }
    26

    View Slide

  27. Reified type parameters Kotlin extension
    inline fun RestOperations.exchange(url: String, method: HttpMethod,
    requestEntity: HttpEntity<*>? = null) =
    exchange(url, method, requestEntity, object : ParameterizedTypeReference() {})
    List list = restTemplate
    .exchange("/api/article/", GET, null, new ParameterizedTypeReference>(){})
    .getBody();
    val list: List = restTemplate
    .exchange("/api/article/", GET)
    .getBody();
    Goodbye type erasure, we are not going to miss you at all!
    27

    View Slide

  28. Null safety
    28
    Nothing enforced at type system
    No check at compile time
    NullPointerException at runtime
    Optional only usable for return values
    Default is non-null, ? suffix for nullable types
    Full check at compile time
    No NullPointerException !!!
    Functional constructs on nullable values

    View Slide

  29. By default, Kotlin interprets Java types as platform types (unknown nullability)
    Null safety of Spring APIs
    public interface RestOperations {
    URI postForLocation(String url,
    Object request,
    Object ... uriVariables)
    }
    29
    postForLocation(url: String!, request: Any!, varags uriVariables: Any!): URI!

    View Slide

  30. Nullability annotations meta annotated with JSR 305 for generic tooling support
    Null safety of Spring APIs
    @NonNullApi
    package org.springframework.web.client;
    public interface RestOperations {
    @Nullable
    URI postForLocation(String url,
    @Nullable Object request,
    Object... uriVariables)
    }
    30
    postForLocation(url: String, request: Any?, varargs uriVariables: Any): URI?
    freeCompilerArgs =
    ["-Xjsr305=strict"]
    +

    View Slide

  31. 31
    @Controller // Mandatory Optional
    class FooController(val foo: Foo, val bar: Bar?) {
    @GetMapping("/") // Equivalent to @RequestParam(required=false)
    fun foo(@RequestParam baz: String?) = ...
    }
    Leveraging Kotlin nullable information
    To determine @RequestParam or @Autowired required attribute

    View Slide

  32. @ConfigurationProperties
    @ConfigurationProperties("foo")
    data class FooProperties(
    var baseUri: String? = null,
    var admin = Credentials()) {
    data class Credential(
    var username: String? = null,
    var password: String? = null)
    }
    One of the remaining pain points in Kotlin

    View Slide

  33. 33
    JUnit 5 supports non-static @BeforeAll @AfterAll
    class IntegrationTests {
    private val application = Application(8181)
    private val client = WebClient.create("http://localhost:8181")
    @BeforeAll
    fun beforeAll() { application.start() }
    @Test
    fun test1() { // ... }
    @Test
    fun test2() { // ... }
    @AfterAll
    fun afterAll() { application.stop() }
    }
    With “per class” lifecycle defined via junit-platform.properties or @TestInstance

    View Slide

  34. 34
    JUnit 5 supports constructor based injection
    @ExtendWith(SpringExtension::class)
    @SpringBootTest(webEnvironment = RANDOM_PORT)
    class HtmlTests(@Autowired val restTemplate: TestRestTemplate) {
    @Test
    fun test1() { // ... }
    @Test
    fun test2() { // ... }
    }
    Allows to use val instead of lateinit var in tests

    View Slide

  35. 35
    class SimpleTests {
    @Nested
    @DisplayName("a calculator")
    inner class Calculator {
    val calculator = SampleCalculator()
    @Test
    fun `should return the result of adding the first number to the second number`() {
    val sum = calculator.sum(2, 4)
    assertEquals(6, sum)
    }
    @Test
    fun `should return the result of subtracting the second number from the first number`() {
    val subtract = calculator.subtract(4, 2)
    assertEquals(2, subtract)
    }
    }
    }
    Specification-like tests with Kotlin and JUnit 5

    View Slide

  36. From Java to Kotlin
    Step 1 Kotlin
    Step 2 Boot 2
    Step 3 WebFlux
    Step 4 Kotlin DSL & Functional API

    View Slide

  37. Spring Framework 5 introduces
    a new web stack
    Spring MVC
    Blocking
    Servlet
    Spring WebFlux
    Non-blocking
    Reactive Streams

    View Slide

  38. 38

    Streaming
    Scalability
    Latency

    View Slide

  39. Servlet Reactive
    Functional
    endpoint
    @Controller
    Reactive client
    @Controller
    RestTemplate
    Netty
    Undertow
    Tomcat Jetty
    Servlet 3.1 container
    Tomcat Jetty
    Servlet container

    View Slide

  40. 40
    RxJava
    ?
    WebFlux supports various non-blocking API
    CompletableFuture
    Flow.Publisher
    Reactor Akka
    Reactive Streams

    View Slide

  41. 41
    Let’s focus on Reactor for now
    RxJava
    CompletableFuture
    Flow.Publisher
    Reactor Akka
    Reactive Streams
    ?

    View Slide

  42. 42
    Mono is a reactive type for 0..1 element

    View Slide

  43. 43
    Flux is for reactive collection and stream

    View Slide

  44. 44
    @RestController
    class ReactiveUserController(val repository: ReactiveUserRepository) {
    @GetMapping("/user/{id}")
    fun findOne(@PathVariable id: String): Mono
    = repository.findOne(id)
    @GetMapping("/user")
    fun findAll(): Flux
    = repository.findAll()
    @PostMapping("/user")
    fun save(@RequestBody user: Mono): Mono
    = repository.save(user)
    }
    interface ReactiveUserRepository {
    fun findOne(id: String): Mono
    fun findAll(): Flux
    fun save(user: Mono): Mono
    }
    Spring WebFlux with annotations
    Spring Data Kay provides
    Reactive support for
    MongoDB, Redis, Cassandra
    and Couchbase

    View Slide

  45. 45
    val location = "Lyon, France"
    mainService.fetchWeather(location)
    .timeout(Duration.ofSeconds(2))
    .doOnError { logger.error(it.getMessage()) }
    .onErrorResume { backupService.fetchWeather(location) }
    .map { "Weather in ${it.getLocation()} is ${it.getDescription()}" }
    .subscribe { logger.info(it) }
    fun fetchWeather(city: String): Mono
    Reactive APIs = functional programming

    View Slide

  46. Reactor Kotlin extensions
    Java Kotlin with extensions
    Mono.just("foo") "foo".toMono()
    Flux.fromIterable(list) list.toFlux()
    Mono.error(new RuntimeException()) RuntimeException().toMono()
    flux.ofType(User.class) flux.ofType()
    StepVerifier.create(flux).verifyComplete() flux.test().verifyComplete()
    MathFlux.averageDouble(flux) flux.average()

    View Slide

  47. From Java to Kotlin
    Step 1 Kotlin
    Step 2 Boot 2
    Step 3 WebFlux
    Step 4 Kotlin DSL & Functional API

    View Slide

  48. 48
    val router = router {
    val users = …
    accept(TEXT_HTML).nest {
    "/" { ok().render("index") }
    "/sse" { ok().render("sse") }
    "/users" {
    ok().render("users", mapOf("users" to users.map { it.toDto() }))
    }
    }
    ("/api/users" and accept(APPLICATION_JSON)) {
    ok().body(users)
    }
    ("/api/users" and accept(TEXT_EVENT_STREAM)) {
    ok().bodyToServerSentEvents(users.repeat().delayElements(ofMillis(100)))
    }
    }
    WebFlux functional API with Kotlin DSL

    View Slide

  49. 49
    @SpringBootApplication
    class Application {
    @Bean
    fun router(htmlHandler: HtmlHandler, userHandler: UserHandler, articleHandler: ArticleHandler) = router {
    accept(APPLICATION_JSON).nest {
    "/api/user".nest {
    GET("/", userHandler::findAll)
    GET("/{login}", userHandler::findOne)
    }
    "/api/article".nest {
    GET("/", articleHandler::findAll)
    GET("/{slug}", articleHandler::findOne)
    POST("/", articleHandler::save)
    DELETE("/{slug}", articleHandler::delete)
    }
    }
    (GET("/api/article/notifications") and accept(TEXT_EVENT_STREAM)).invoke(articleHandler::notifications)
    accept(TEXT_HTML).nest {
    GET("/", htmlHandler::blog)
    (GET("/{slug}") and !GET("/favicon.ico")).invoke(htmlHandler::article)
    }
    }
    }
    Functional router within Boot

    View Slide

  50. @Component
    class HtmlHandler(private val userRepository: UserRepository,
    private val converter: MarkdownConverter) {
    fun blog(req: ServerRequest) = ok().render("blog", mapOf(
    "title" to "Blog",
    "articles" to articleRepository.findAll()
    .flatMap { it.toDto(userRepository, converter) } ))
    }
    @Component
    class ArticleHandler(private val articleRepository: ArticleRepository,
    private val articleEventRepository: ArticleEventRepository) {
    fun findAll(req: ServerRequest) =
    ok().body(articleRepository.findAll())
    fun notifications(req: ServerRequest) =
    ok().bodyToServerSentEvents(articleEventRepository.findWithTailableCursorBy())
    }
    50
    Functional handlers within Boot

    View Slide

  51. Under construction ...
    Fixing remaining pain points
    Coroutines
    Functional bean definition
    Multi-platform

    View Slide

  52. Immutable non-nullable @ConfigurationProperties
    @ConfigurationProperties("foo")
    data class FooProperties(
    val baseUri: String,
    val admin: Credential) {
    data class Credential(
    val username: String,
    val password: String)
    }
    Boot 2.1 ?
    See issue #8762 for more details
    @ConfigurationProperties("foo")
    data class FooProperties(
    lateinit var baseUri: String,
    var admin = Credentials()) {
    data class Credential(
    lateinit var username: String,
    lateinit var password: String)
    }
    Currently
    N
    ot yet available

    View Slide

  53. WebTestClient
    Unusable in Kotlin due to a type inference issue, see KT-5464
    otlin 1.3
    For now, please use:
    - WebClient in Kotlin
    - WebTestClient in Java

    View Slide

  54. Under construction ...
    Fixing remaining pain points
    Coroutines
    Boot + functional bean DSL
    Multi-platform

    View Slide

  55. 55
    Kotlin Coroutines
    RxJava
    CompletableFuture
    Flow.Publisher
    Reactor Akka
    Reactive Streams
    Coroutines
    Experimental

    View Slide

  56. Spring & Kotlin Coroutines
    56
    ● Coroutines are Kotlin lightweight threads
    ● Allows non-blocking imperative code
    ● Less powerful than Reactive API (backpressure, streaming, operators, etc.)
    ● kotlinx.coroutines: Reactive Streams and Reactor support
    ● spring-kotlin-coroutine: experimental Spring support (community driven)
    ● Warning
    ○ Coroutine are still experimental
    ○ No official Spring support yet, see SPR-15413
    ○ Ongoing evaluation of performances and back-pressure interoperability
    Experim
    ental
    https://github.com/konrad-kaminski/spring-kotlin-coroutine

    View Slide

  57. Reactive Coroutines interop
    57
    Operation Reactive Coroutines
    Async value fun foo(): Mono suspend fun foo(): T?
    Async collection fun foo(): Mono> suspend fun foo(): List
    Stream fun foo(): Flux suspend fun foo(): ReceiveChannel
    Async completion fun foo(): Mono suspend fun foo()

    View Slide

  58. 58
    @RestController
    class CoroutineUserController(val repository: CoroutineUserRepository) {
    @GetMapping("/user/{id}")
    suspend fun findOne(@PathVariable id: String): User
    = repository.findOne(id)
    @GetMapping("/user")
    suspend fun findAll(): List
    = repository.findAll()
    @PostMapping("/user")
    suspend fun save(@RequestBody user: User)
    = repository.save(user)
    }
    interface CoroutineUserRepository {
    suspend fun findOne(id: String): User
    suspend fun findAll(): List
    suspend fun save(user: User)
    }
    Spring WebFlux with Coroutines Experim
    ental
    https://github.com/sdeleuze/spring-kotlin-deepdive/tree/step3-coroutine

    View Slide

  59. Under construction ...
    Fixing remaining pain points
    Coroutines
    Boot + functional bean DSL
    Multi-platform

    View Slide

  60. Spring Framework 5 introduces
    Functional bean definition
    Very efficient, no reflection, no CGLIB proxy
    Lambdas instead of annotations
    Both declarative and programmatic

    View Slide

  61. 61
    Functional bean definition Kotlin DSL
    val databaseContext =
    beans {
    bean()
    bean()
    bean()
    bean()
    environment( { !activeProfiles.contains("cloud") } ) {
    bean {
    InitializingBean {
    initializeDatabase(ref(), ref(), ref())
    }
    }
    }
    }
    fun initializeDatabase(ops: MongoOperations,
    userRepository: UserRepository,
    articleRepository: ArticleRepository) { // ... }

    View Slide

  62. 62
    Beans + router DSL fit well together
    val context =
    beans {
    bean {
    val userHandler = ref()
    router {
    accept(APPLICATION_JSON).nest {
    "/api/user".nest {
    GET("/", userHandler::findAll)
    GET("/{login}", userHandler::findOne)
    }
    }
    }
    bean { Mustache.compiler().escapeHTML(false).withLoader(ref()) }
    bean()
    bean()
    bean()
    bean()
    }

    View Slide

  63. 63
    Bean DSL is extensible and composable !
    webfluxApplication(Server.NETTY) { // or TOMCAT
    // group routers
    routes {
    router { routerApi(ref()) }
    router(routerStatic())
    }
    router { routerHtml(ref(), ref()) }
    // group beans
    beans {
    bean()
    bean() // Primary constructor injection
    }
    bean()
    mustacheTemplate()
    profile("foo") {
    bean()
    }
    }
    See https://github.com/tgirard12/spring-webflux-kotlin-dsl POC

    View Slide

  64. Functional bean definition with Spring Boot
    64
    @SpringBootApplication
    class Application
    fun main(args: Array) {
    runApplication(*args) {
    addInitializers(databaseContext, webContext)
    }
    }
    This nice syntax currently works only for running the application, not tests

    View Slide

  65. Functional bean definition with Spring Boot
    65
    class ContextInitializer : ApplicationContextInitializer {
    override fun initialize(context: GenericApplicationContext) {
    databaseContext.initialize(context)
    webContext.initialize(context)
    }
    }
    context.initializer.classes=io.spring.deepdive.ContextInitializer
    This more verbose one works for running both application and tests

    View Slide

  66. Functional bean definition with Spring Boot
    66
    Come discussing next steps with us

    View Slide

  67. Under construction ...
    Fixing remaining pain points
    Coroutines
    Boot + functional bean DSL
    Multi-platform

    View Slide

  68. Kotlin is multi-platform
    Kotlin/JVM: JVM and Android
    Kotlin/JS: transpile to JavaScript
    Kotlin/Native: run without any VM (LLVM toolchain)

    View Slide

  69. 69
    Kotlin for frontend today with Kotlin/JS

    View Slide

  70. 70
    Original JavaScript code
    if (Notification.permission === "granted") {
    Notification.requestPermission().then(function(result) {
    console.log(result);
    });
    }
    let eventSource = new EventSource("/api/article/notifications");
    eventSource.addEventListener("message", function(e) {
    let article = JSON.parse(e.data);
    let notification = new Notification(article.title);
    notification.onclick = function() {
    window.location.href = "/" + article.slug;
    };
    });

    View Slide

  71. 71
    Kotlin to Javascript
    data class Article(val slug: String, val title: String)
    fun main(args: Array) {
    if (Notification.permission == NotificationPermission.GRANTED) {
    Notification.requestPermission().then { console.log(it) }
    }
    EventSource("/api/article/notifications").addEventListener("message", {
    val article = JSON.parse(it.data());
    Notification(article.title).addEventListener("click", {
    window.location.href = "/${article.slug}"
    })
    })
    }
    fun Event.data() = (this as MessageEvent).data as String // See KT-20743
    Type safety, null safety, only 10 Kbytes with Dead Code Elimination tool

    View Slide

  72. 72
    Kotlin for frontend tomorrow with WebAssembly
    Read “An Abridged Cartoon Introduction To WebAssembly” by Lin Clark for more details
    https://goo.gl/I0kQsC
    A sandboxed native platform for the Web (W3C, no plugin involved)

    View Slide

  73. 73
    Compiling Kotlin to WebAssembly
    +
    ➔ Kotlin supports WebAssembly via Kotlin/Native
    ➔ Better compilation target than JS: bytecode, performance, memory
    ➔ DOM API and GC are coming ...
    ➔ A Kotlin/Native Frontend ecosystem could arise
    Experim
    ental

    View Slide

  74. Kotlin 1.2 allows sharing code
    between platforms
    Multi-platform data types and serialization are coming

    View Slide

  75. Thanks!
    https://speakerdeck.com/sdeleuze/why-spring-loves-kotlin
    Follow me on @sdeleuze for fresh Spring + Kotlin news
    https://github.com/mixitconf/mixit
    https://github.com/sdeleuze/spring-kotlin-fullstack
    https://github.com/sdeleuze/spring-kotlin-deepdive
    https://github.com/sdeleuze/spring-kotlin-functional

    View Slide