SpringOne Platform 2017
Sébastien Deleuze, Pivotal
"In this new talk, I will explain why Spring <3 Kotlin and how you can leverage Spring official support for Kotlin (in Framework, Boot, Data) to build your next Spring project more efficiently and with more pleasure.
I will describe gradually how you can transform your Spring Boot 1.0 Java + Javascript project with into a Spring Boot 2.0 pure Kotlin project running on top of the new WebFlux functional web framework."
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;
}
13. 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. 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 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. 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”
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
18. From Java to Kotlin
Step 1 Kotlin
Step 2 Boot 2
Step 3 WebFlux
Step 4 Kotlin DSL & Functional API
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
22. Running Spring Boot 1 application with Kotlin
22
@SpringBootApplication
class Application
fun main(args: Array<String>) {
SpringApplication.run(Application::class.java, *args)
}
23. Running Spring Boot 2 application with Kotlin
23
@SpringBootApplication
class Application
fun main(args: Array<String>) {
runApplication<FooApplication>(*args)
}
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
27. 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
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
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!
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"]
+
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
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
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
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
36. From Java to Kotlin
Step 1 Kotlin
Step 2 Boot 2
Step 3 WebFlux
Step 4 Kotlin DSL & Functional API
37. Spring Framework 5 introduces
a new web stack
Spring MVC
Blocking
Servlet
Spring WebFlux
Non-blocking
Reactive Streams
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
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
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.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 Kotlin due to a type inference issue, see KT-5464
otlin 1.3
For now, please use:
- WebClient in Kotlin
- WebTestClient in Java
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
57. 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. 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
60. Spring Framework 5 introduces
Functional bean definition
Very efficient, no reflection, no CGLIB proxy
Lambdas instead of annotations
Both declarative and programmatic
62. 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. 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
64. 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
65. 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
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
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. 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. 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
74. Kotlin 1.2 allows sharing 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