Why Spring Kotlin
Sébastien Deleuze
@sdeleuze
2
Most popular way to build web applications
+
Spring
Boot
3
Let’s see how far we can go with ...
Kotlin
+
Spring
Boot
4
5
Migrating a typical Boot application to Kotlin
https://github.com/sdeleuze/spring-kotlin-deepdive
6
Step 1
Kotlin
7
Step 2
Spring Boot 1 Spring Boot 2
based on Spring Framework 5
8
Step 3
Spring MVC Spring WebFlux
9
Step 4
Spring WebFlux
Functional API & Kotlin DSL
Spring WebFlux
@nnotations
From Java to Kotlin
Step 1 Kotlin
Step 2 Boot 2
Step 3 WebFlux
Step 4 Kotlin DSL & Functional API
11
https://start.spring.io/#!language=kotlin
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;
}
13
Expressive test function names with backticks
class EmojTests {
@Test
fun `Why Spring ❤ Kotlin?`() {
println("Because I can use emoj in function names uD83DuDE09")
}
}
> Because I can use emoj in function names
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
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
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”
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
From Java to Kotlin
Step 1 Kotlin
Step 2 Boot 2
Step 3 WebFlux
Step 4 Kotlin DSL & Functional API
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
20
Kotlin support out of the box
21
Kotlin support documentation
https://goo.gl/uwyjQn
Running Spring Boot 1 application with Kotlin
22
@SpringBootApplication
class Application
fun main(args: Array<String>) {
SpringApplication.run(Application::class.java, *args)
}
Running Spring Boot 2 application with Kotlin
23
@SpringBootApplication
class Application
fun main(args: Array<String>) {
runApplication<FooApplication>(*args)
}
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)
}
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)
}
}
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
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
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
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!
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"]
+
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
@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
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
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
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
From Java to Kotlin
Step 1 Kotlin
Step 2 Boot 2
Step 3 WebFlux
Step 4 Kotlin DSL & Functional API
Spring Framework 5 introduces
a new web stack
Spring MVC
Blocking
Servlet
Spring WebFlux
Non-blocking
Reactive Streams
38
∞
Streaming
Scalability
Latency
∞
40
RxJava
?
WebFlux supports various non-blocking API
CompletableFuture
Flow.Publisher
Reactor Akka
41
Let’s focus on Reactor for now
RxJavaCompletableFuture
Flow.Publisher
Reactor Akka
?
42
Mono is a reactive type for 0..1 element
43
Flux is for reactive collection and stream
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
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
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()
From Java to Kotlin
Step 1 Kotlin
Step 2 Boot 2
Step 3 WebFlux
Step 4 Kotlin DSL & Functional API
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
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
@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
Under construction ...
Fixing remaining pain points
Coroutines
Functional bean definition
Multi-platform
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.x ?
See issue #8762 for more details
@ConfigurationProperties("foo")
data class FooProperties(
var baseUri: String? = null,
var admin = Credentials()) {
data class Credential(
var username: String? = null,
var password: String? = null)
}
Currently
N
ot yet available
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
Under construction ...
Fixing remaining pain points
Coroutines
Boot + functional bean DSL
Multi-platform
55
Kotlin Coroutines
RxJavaCompletableFuture
Flow.Publisher
Reactor Akka
Coroutines
Experimental
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
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()
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
Under construction ...
Fixing remaining pain points
Coroutines
Boot + functional bean DSL
Multi-platform
Spring Framework 5 introduces
Functional bean definition
Very efficient, no reflection, no CGLIB proxy
Lambdas instead of annotations
Both declarative and programmatic
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) { // ... }
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>()
}
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
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
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
Functional bean definition with Spring Boot
66
Come discussing next steps with us
Under construction ...
Fixing remaining pain points
Coroutines
Boot + functional bean DSL
Multi-platform
Kotlin is multi-platform
Kotlin/JVM: JVM and Android
Kotlin/JS: transpile to JavaScript
Kotlin/Native: run without any VM (LLVM toolchain)
69
Kotlin for frontend today with Kotlin/JS
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;
};
});
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
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)
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
Kotlin 1.2 allows sharing code
between platforms
Multi-platform data types and serialization are coming
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

Why Spring <3 Kotlin

  • 1.
  • 2.
    2 Most popular wayto build web applications + Spring Boot
  • 3.
    3 Let’s see howfar we can go with ... Kotlin + Spring Boot
  • 4.
  • 5.
    5 Migrating a typicalBoot application to Kotlin https://github.com/sdeleuze/spring-kotlin-deepdive
  • 6.
  • 7.
    7 Step 2 Spring Boot1 Spring Boot 2 based on Spring Framework 5
  • 8.
    8 Step 3 Spring MVCSpring WebFlux
  • 9.
    9 Step 4 Spring WebFlux FunctionalAPI & Kotlin DSL Spring WebFlux @nnotations
  • 10.
    From Java toKotlin Step 1 Kotlin Step 2 Boot 2 Step 3 WebFlux Step 4 Kotlin DSL & Functional API
  • 11.
  • 12.
    12 Domain model @Document data classArticle( @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; }
  • 13.
    13 Expressive test functionnames with backticks class EmojTests { @Test fun `Why Spring ❤ Kotlin?`() { println("Because I can use emoj in function names uD83DuDE09") } } > Because I can use emoj in function names
  • 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<User> findAll() { return userRepository.findAll(); } @PostMapping("/user") public User save(@RequestBody User user) { return userRepository.save(user); } } Spring MVC controller written in Java
  • 15.
    15 @RestController class UserController(private valrepo: 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
  • 16.
    16 Inferred type hintsin IDEA Settings Editor General Appearance Show parameter name hints Select Kotlin Check “Show function/property/local value return type hints”
  • 17.
    kotlin-spring compiler plugin Automaticallyopen 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
  • 18.
    From Java toKotlin Step 1 Kotlin Step 2 Boot 2 Step 3 WebFlux Step 4 Kotlin DSL & Functional API
  • 19.
    Spring Kotlin and officiallysupports it Spring Framework 5 Spring Boot 2.0 (M7 available, GA early 2018) Reactor Core 3.1 Spring Data Kay
  • 20.
  • 21.
  • 22.
    Running Spring Boot1 application with Kotlin 22 @SpringBootApplication class Application fun main(args: Array<String>) { SpringApplication.run(Application::class.java, *args) }
  • 23.
    Running Spring Boot2 application with Kotlin 23 @SpringBootApplication class Application fun main(args: Array<String>) { runApplication<FooApplication>(*args) }
  • 24.
    Declaring additional beans 24 @SpringBootApplication classApplication { @Bean fun foo() = Foo() @Bean fun bar(foo: Foo) = Bar(foo) } fun main(args: Array<String>) { runApplication<FooApplication>(*args) }
  • 25.
    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) } }
  • 26.
    Array-like Kotlin extensionfor 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
  • 27.
    Reified type parametersKotlin 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
  • 28.
    Null safety 28 Nothing enforcedat 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
  • 29.
    By default, Kotlininterprets 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!
  • 30.
    Nullability annotations metaannotated 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"] +
  • 31.
    31 @Controller // MandatoryOptional 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
  • 32.
    @ConfigurationProperties @ConfigurationProperties("foo") data class FooProperties( varbaseUri: String? = null, var admin = Credentials()) { data class Credential( var username: String? = null, var password: String? = null) } One of the remaining pain points in Kotlin
  • 33.
    33 JUnit 5 supportsnon-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
  • 34.
    34 JUnit 5 supportsconstructor 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
  • 35.
    35 class SimpleTests { @Nested @DisplayName("acalculator") 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
  • 36.
    From Java toKotlin Step 1 Kotlin Step 2 Boot 2 Step 3 WebFlux Step 4 Kotlin DSL & Functional API
  • 37.
    Spring Framework 5introduces a new web stack Spring MVC Blocking Servlet Spring WebFlux Non-blocking Reactive Streams
  • 38.
  • 40.
    40 RxJava ? WebFlux supports variousnon-blocking API CompletableFuture Flow.Publisher Reactor Akka
  • 41.
    41 Let’s focus onReactor for now RxJavaCompletableFuture Flow.Publisher Reactor Akka ?
  • 42.
    42 Mono is areactive type for 0..1 element
  • 43.
    43 Flux is forreactive collection and stream
  • 44.
    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
  • 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<Weather> Reactive APIs = functional programming
  • 46.
    Reactor Kotlin extensions JavaKotlin 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()
  • 47.
    From Java toKotlin Step 1 Kotlin Step 2 Boot 2 Step 3 WebFlux Step 4 Kotlin DSL & Functional API
  • 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
  • 49.
    49 @SpringBootApplication class Application { @Bean funrouter(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
  • 50.
    @Component class HtmlHandler(private valuserRepository: 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
  • 51.
    Under construction ... Fixingremaining pain points Coroutines Functional bean definition Multi-platform
  • 52.
    Immutable non-nullable @ConfigurationProperties @ConfigurationProperties("foo") dataclass FooProperties( val baseUri: String, val admin: Credential) { data class Credential( val username: String, val password: String) } Boot 2.x ? See issue #8762 for more details @ConfigurationProperties("foo") data class FooProperties( var baseUri: String? = null, var admin = Credentials()) { data class Credential( var username: String? = null, var password: String? = null) } Currently N ot yet available
  • 53.
    WebTestClient Unusable in Kotlindue to a type inference issue, see KT-5464 otlin 1.3 For now, please use: - WebClient in Kotlin - WebTestClient in Java
  • 54.
    Under construction ... Fixingremaining pain points Coroutines Boot + functional bean DSL Multi-platform
  • 55.
  • 56.
    Spring & KotlinCoroutines 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
  • 57.
    Reactive Coroutines interop 57 OperationReactive 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()
  • 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<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
  • 59.
    Under construction ... Fixingremaining pain points Coroutines Boot + functional bean DSL Multi-platform
  • 60.
    Spring Framework 5introduces Functional bean definition Very efficient, no reflection, no CGLIB proxy Lambdas instead of annotations Both declarative and programmatic
  • 61.
    61 Functional bean definitionKotlin 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) { // ... }
  • 62.
    62 Beans + routerDSL 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>() }
  • 63.
    63 Bean DSL isextensible 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
  • 64.
    Functional bean definitionwith 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
  • 65.
    Functional bean definitionwith 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
  • 66.
    Functional bean definitionwith Spring Boot 66 Come discussing next steps with us
  • 67.
    Under construction ... Fixingremaining pain points Coroutines Boot + functional bean DSL Multi-platform
  • 68.
    Kotlin is multi-platform Kotlin/JVM:JVM and Android Kotlin/JS: transpile to JavaScript Kotlin/Native: run without any VM (LLVM toolchain)
  • 69.
    69 Kotlin for frontendtoday with Kotlin/JS
  • 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; }; });
  • 71.
    71 Kotlin to Javascript dataclass 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
  • 72.
    72 Kotlin for frontendtomorrow 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)
  • 73.
    73 Compiling Kotlin toWebAssembly + ➔ 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
  • 74.
    Kotlin 1.2 allowssharing code between platforms Multi-platform data types and serialization are coming
  • 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