- DSL in Kotlin
- Kotlin and Spring Boot, a match made in heaven
- GraphQL powered by Kotlin
- Architecting a Kotlin JVM and JS multiplatform project
- Exploring the Kotlin type hierarchy from top to bottom
- New Type Inference and Related Language Features
- Representing State: the Kotlin Edition
- Exploring Coroutines in Kotlin
- Functional Programming in Kotlin with Λrrow
- Building Server Backends with Ktor
- Best Practices for Unit Testing in Kotlin
- Creating Internal DSLs in Kotlin
When you really wish your language could do the thing
Start from the result that you want to enable
… then write the code thant enables it !
@DslMarker
: prevent scoping mishaps !
-
Dont' pollute the global namespace
-
Unary
+
only if well-scoped -
Keep lambda files next to your builder classes
-
Don’t extend system types (String, Int, …)
42 { //bad
.....
}
100.dollarsToCent() // Good
@nicolas_frankel
🤯 The issue : MAGIC !
🧐 The solution ? : functional configuration
Switch to reactive :
-
JPA → Mongo Reactive
-
WebMVC → WebFlux
Swith to static configuration (no magic) :
-
RestController → static route definitions → Router definition DSL
-
@Beans → Bean Definition DSL
-
context.initializer.classes
property → programmtically register BeansInitializer
Warning
|
Experimental 😜 |
val beans = beans {
bean<PersonHandler>()
bean<PersonRepository>()
}
val app = application {
import(beans)
listener<ApplicationReadyEvent> {
ref<PersonRepository>().insert(
arrayListOf(Person(1, "John", "Doe", LocalDate.of(1970, 1, 1)),
Person(2, "Jane", "Doe", LocalDate.of(1970, 1, 1)),
Person(3, "Brian", "Goetz"))
).blockLast(Duration.ofSeconds(2))
}
server {
import(::routes)
codecs {
jackson()
}
}
mongodb {
embedded()
}
}
fun routes(handler: PersonHandler) = router {
"/person".nest {
GET("/{id}", handler::readOne)
GET("/", handler::readAll)
}
}
class PersonHandler(private val personRepository: PersonRepository) {
fun readAll(request: ServerRequest) = ServerResponse.ok().body(personRepository.findAll())
fun readOne(request: ServerRequest) = ServerResponse.ok().body(personRepository.findById(request.pathVariable("id").toLong()))
}
fun main(args: Array<String>) {
app.run(args)
}
@Document
class Person(@Id val id: Long, val firstName: String, val lastName: String, val birthdate: LocalDate? = null)
class PersonRepository(private val mongo: ReactiveMongoOperations) {
fun findAll() = mongo.findAll<Person>()
fun findById(id: Long) = mongo.findById<Person>(id)
fun insert(persons: List<Person>) = mongo.insert(persons, Person::class)
}
type UFOSighting {
id : Int!
city: String
}
type <UFOSighting>
data class UFOSighting {
id : Int = -1
city: String?
}
Generate Java Client from schema.json
-
Build your request
-
Enqueue the resquest
-
Handle the response
Tip
|
|
Ideal for businnes logic code sharing
Kotlin Multiplatform != React Native
Kotlin Multiplatform > C / C++
Common
→ kotlinc (JVM, Android)
→ Kotlin/Native (Executable, Dynamic lib, iOS)
→ kotlin2js (Javascript)
-
apply plugin: 'kotlin-platform-common'
-
apply plugin: 'kotlin-platform-jvm'
-
apply plugin: 'org.jetbrains.kotlin.frontend
-
…
expect class Order {
val id: Int
val userId: Int
}
actual data class Order {
val id: Int
val userId: Int
}
expect
is not interface !
-
simplier implementation
-
can have a constructor
-
all implementations are known at compile time
-
more flexibility
-
top level and extension functions are supported
Warning
|
|
Tip
|
you need to explicity opt in at the call site to use experimental features : kotlin { experimental { contracts 'enable'}
|
Tip
|
you can mark your experimental API with :
|
We know something about run, which the compiler doesn’t
Contracts allow to share extra information about code semantics with the compiler
-
Making smart casts even smarter
fun String?.isNullOrEmpty(): Boolean {
contract {
returns(false) implies (this@isNullOrEmpty != null)
}
return this == "" || this == null
}
val s: String? = ""
if (!s.isNullOrEmpty) {
s.first() // ✅
}
-
Better and more powerful type inference
-
New Features are supported
kotlin { experimental { newInference 'enable'}
Tip
|
Libraries should specify return types for public API : turn on the IDE inspection ("Public API delcaration has implicit return type") |
-
Function Interface conversions for Kotlin functions
-
better inference for builders
-
better inference for call chains
-
better inference for intersection types
👻 Boolean Blindness ⇒ work with more expressive types !
-
use
sealed
classes (everywhere !) -
use
interfaces
for boolean representation
💥 Strings are danger (same for Int) ⇒ infinite input 😱
class IllogicalPerson {
var heart: Heart?
var head: Head?
var arms: List<Arm>
var legs: List<Leg>
}
class LogicalPerson {
var heart: Heart
val head: Head
val arms: Pair<Arm?,Arm?>
val legs: Pair<Leg?,Leg?>
}
😻😻😻😻
Structure of [functional] sequential code is the same as parallel code
Structure of [imperative] synchronous code is the same as asynchronous code
🤩😎🤩😎🤩😎🤩😎🤩😎🤩😎
-
with data class for simple case
-
Arrow provides
optics
withlens
(https://arrow-kt.io/docs/optics/dsl/#optics-dsl)
-
Don’t throw exceptions → use
Either
andTry
but it’s synchronous -
Arrow provides Monad Transformers :
EitherT
-
Higher kinded types : abstract away the computational container type
-
`class Option<A> : OptionOf<A>
-
MR to add Type Class in Kotlin : Kotlin/KEEP#87
interface Repository<A> {
fun A.save(): A
fun cache(): List<A>
}
Composable, DSL based web services in Kotlin
Warning
|
1.0 waiting for Kotlin 1.3 (corountine no more experimental) |
fun Application.verify() {
install(StatusPages) {...}
install(ContentNegociation) {...}
routing {
post("/verify") {
call.respond(Response(status="OK")
}
}
}
data class Response(val status: String)
-
Reuse the Test Class Instance :
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
→ you can useprivate val
field orinit {…}
block
TIP : you can set the lifecycle by defaut in junit-platform.properties file
(no more need annotation)
-
Use backticks for test name
-
Use
@Nested
to group tests (by tested method of class for instance)
Warning
|
☠️ Classes Are Final by Default |
⇒ use MockK
(https://mockk.io/)
-
Don’t recreate Mocks ! (it’s expensive : 2,1s → 0,4s )
@BeforeEach
fun init() {
clearMocks(repo, client)
}
-
Use maven plugin
allopen
to deal with final classes (✅) -
Use constructor injection (👍)
-
Use for Assertions !
Tip
|
assertThat(…).isEqualToIgnoringGivendFields(…, "id")
|
-
Helper function for Object Creation (use default values for data class args)
-
Data classes for Parameterized Test with
@MethodSource