Slide 1

Slide 1 text

Why Spring Kotlin Sébastien Deleuze @sdeleuze

Slide 2

Slide 2 text

2 Most popular way to build web applications + Spring Boot

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

4

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

6 Step 1 Kotlin

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

8 Step 3 Spring MVC Spring WebFlux

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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; }

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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”

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

20 Kotlin support out of the box

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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) } }

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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!

Slide 30

Slide 30 text

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"] +

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

@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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

38 ∞ Streaming Scalability Latency ∞

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

43 Flux is for reactive collection and stream

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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()

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

@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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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()

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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) { // ... }

Slide 62

Slide 62 text

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() }

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

69 Kotlin for frontend today with Kotlin/JS

Slide 70

Slide 70 text

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; }; });

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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)

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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