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. 4

  2. From Java to Kotlin Step 1 Kotlin Step 2 Boot

    2 Step 3 WebFlux Step 4 Kotlin DSL & Functional API
  3. 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; }
  4. 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
  5. 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<User> findAll() { return userRepository.findAll(); } @PostMapping("/user") public User save(@RequestBody User user) { return userRepository.save(user); } } Spring MVC controller written in Java
  6. 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
  7. 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”
  8. 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
  9. From Java to Kotlin Step 1 Kotlin Step 2 Boot

    2 Step 3 WebFlux Step 4 Kotlin DSL & Functional API
  10. 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
  11. Running Spring Boot 1 application with Kotlin 22 @SpringBootApplication class

    Application fun main(args: Array<String>) { SpringApplication.run(Application::class.java, *args) }
  12. Running Spring Boot 2 application with Kotlin 23 @SpringBootApplication class

    Application fun main(args: Array<String>) { runApplication<FooApplication>(*args) }
  13. Declaring additional beans 24 @SpringBootApplication class Application { @Bean fun

    foo() = Foo() @Bean fun bar(foo: Foo) = Bar(foo) } fun main(args: Array<String>) { runApplication<FooApplication>(*args) }
  14. Customizing SpringApplication 25 @SpringBootApplication class Application { @Bean fun foo()

    = Foo() @Bean fun bar(foo: Foo) = Bar(foo) } fun main(args: Array<String>) { runApplication<FooApplication>(*args) { setBannerMode(OFF) } }
  15. 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
  16. Reified type parameters Kotlin extension inline fun <reified T: Any>

    RestOperations.exchange(url: String, method: HttpMethod, requestEntity: HttpEntity<*>? = null) = exchange(url, method, requestEntity, object : ParameterizedTypeReference<T>() {}) List<Article> list = restTemplate .exchange("/api/article/", GET, null, new ParameterizedTypeReference<List<Article>>(){}) .getBody(); val list: List<Article> = restTemplate .exchange("/api/article/", GET) .getBody(); Goodbye type erasure, we are not going to miss you at all! 27
  17. 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
  18. 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!
  19. 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"] +
  20. 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
  21. @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
  22. 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
  23. 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
  24. 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
  25. From Java to Kotlin Step 1 Kotlin Step 2 Boot

    2 Step 3 WebFlux Step 4 Kotlin DSL & Functional API
  26. Spring Framework 5 introduces a new web stack Spring MVC

    Blocking Servlet Spring WebFlux Non-blocking Reactive Streams
  27. Servlet Reactive Functional endpoint @Controller Reactive client @Controller RestTemplate Netty

    Undertow Tomcat Jetty Servlet 3.1 container Tomcat Jetty Servlet container
  28. 44 @RestController class ReactiveUserController(val repository: ReactiveUserRepository) { @GetMapping("/user/{id}") fun findOne(@PathVariable

    id: String): Mono<User> = repository.findOne(id) @GetMapping("/user") fun findAll(): Flux<User> = repository.findAll() @PostMapping("/user") fun save(@RequestBody user: Mono<User>): Mono<Void> = repository.save(user) } interface ReactiveUserRepository { fun findOne(id: String): Mono<User> fun findAll(): Flux<User> fun save(user: Mono<User>): Mono<Void> } Spring WebFlux with annotations Spring Data Kay provides Reactive support for MongoDB, Redis, Cassandra and Couchbase
  29. 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<Weather> Reactive APIs = functional programming
  30. 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<User>() StepVerifier.create(flux).verifyComplete() flux.test().verifyComplete() MathFlux.averageDouble(flux) flux.average()
  31. From Java to Kotlin Step 1 Kotlin Step 2 Boot

    2 Step 3 WebFlux Step 4 Kotlin DSL & Functional API
  32. 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
  33. 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
  34. @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
  35. 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
  36. 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
  37. 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
  38. Reactive Coroutines interop 57 Operation Reactive Coroutines Async value fun

    foo(): Mono<T> suspend fun foo(): T? Async collection fun foo(): Mono<List<T>> suspend fun foo(): List<T> Stream fun foo(): Flux<T> suspend fun foo(): ReceiveChannel<T> Async completion fun foo(): Mono<Void> suspend fun foo()
  39. 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<User> = 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<User> suspend fun save(user: User) } Spring WebFlux with Coroutines Experim ental https://github.com/sdeleuze/spring-kotlin-deepdive/tree/step3-coroutine
  40. Spring Framework 5 introduces Functional bean definition Very efficient, no

    reflection, no CGLIB proxy Lambdas instead of annotations Both declarative and programmatic
  41. 61 Functional bean definition Kotlin DSL val databaseContext = beans

    { bean<ArticleEventListener>() bean<ArticleEventRepository>() bean<ArticleRepository>() bean<UserRepository>() environment( { !activeProfiles.contains("cloud") } ) { bean { InitializingBean { initializeDatabase(ref(), ref(), ref()) } } } } fun initializeDatabase(ops: MongoOperations, userRepository: UserRepository, articleRepository: ArticleRepository) { // ... }
  42. 62 Beans + router DSL fit well together val context

    = beans { bean { val userHandler = ref<UserHandler>() router { accept(APPLICATION_JSON).nest { "/api/user".nest { GET("/", userHandler::findAll) GET("/{login}", userHandler::findOne) } } } bean { Mustache.compiler().escapeHTML(false).withLoader(ref()) } bean<HtmlHandler>() bean<ArticleHandler>() bean<UserHandler>() bean<MarkdownConverter>() }
  43. 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<UserHandler>() bean<Baz>() // Primary constructor injection } bean<Bar>() mustacheTemplate() profile("foo") { bean<Foo>() } } See https://github.com/tgirard12/spring-webflux-kotlin-dsl POC
  44. Functional bean definition with Spring Boot 64 @SpringBootApplication class Application

    fun main(args: Array<String>) { runApplication<FooApplication>(*args) { addInitializers(databaseContext, webContext) } } This nice syntax currently works only for running the application, not tests
  45. Functional bean definition with Spring Boot 65 class ContextInitializer :

    ApplicationContextInitializer<GenericApplicationContext> { 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
  46. Kotlin is multi-platform Kotlin/JVM: JVM and Android Kotlin/JS: transpile to

    JavaScript Kotlin/Native: run without any VM (LLVM toolchain)
  47. 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; }; });
  48. 71 Kotlin to Javascript data class Article(val slug: String, val

    title: String) fun main(args: Array<String>) { if (Notification.permission == NotificationPermission.GRANTED) { Notification.requestPermission().then { console.log(it) } } EventSource("/api/article/notifications").addEventListener("message", { val article = JSON.parse<Article>(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
  49. 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)
  50. 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
  51. 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