Slide 1

Slide 1 text

Copenhagen Denmark CREATING FULL- STACK WEB APPS WITH KOTLIN DSLS PAMELA HILL @pamelaahill

Slide 2

Slide 2 text

HELLO MOLO HALLO THOBELA DUMELA LUMELA ABUSHENI SANIBONA AVUWANI SALIBONANI SAWUBONA HELLO MOLO HALLO THOBELA DUMELA LUMELA ABUSHENI SANIBONA AVUWANI SALIBONANI

Slide 3

Slide 3 text

1. PROJECT INTRODUCTION What shall we build today?

Slide 4

Slide 4 text

Wikipedia A VISION BOARD IS A COLLAGE OF IMAGES, PICTURES, AND AFFIRMATIONS OF ONE'S DREAMS AND DESIRES, DESIGNED TO SERVE AS A SOURCE OF INSPIRATION AND MOTIVATION.

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

Kotlin/JS Unsplash Ktor Client H2 Exposed Ktor Server Axios Deployment Location

Slide 8

Slide 8 text

2. THE PROJECT PHILOSOPHY How shall we build today?

Slide 9

Slide 9 text

Ward Cunningham YOU CAN CALL IT BEAUTIFUL CODE WHEN THE CODE MAKES IT LOOK LIKE THE LANGUAGE WAS MADE FOR THE PROBLEM.

Slide 10

Slide 10 text

Why internal DSLs? . Concise Readable Maintainable

Slide 11

Slide 11 text

What makes Kotlin a great language for DSLs? Optional semicolon Infix functions No parentheses for last lambda argument Operators overloading using conventions Extension functions Lambdas with receivers Invoke convention Venkat Subramaniam, Creating Internal DSLS in Kotlin, Kotlinconf 2018

Slide 12

Slide 12 text

Lambdas with receivers Lambdas that are executed within the context of a receiver. val isEven: Int.() -> Boolean = { this % 2 == 0 } println(294823098.isEven())

Slide 13

Slide 13 text

3. BUILDING THE BACKEND WITH KTOR

Slide 14

Slide 14 text

Ktor is a Kotlin framework for creating asynchronous servers and clients Built on Kotlin and coroutines Easy setup using IntelliJ Plugin or generator at start.ktor.io Use DSL to specify application behaviour ThoughtWorks Trial category since April 2019

Slide 15

Slide 15 text

ThoughtWorks, April 2019 THE FLEXIBILITY TO INCORPORATE DIFFERENT TOOLS IN ADDITION TO ITS LIGHT-WEIGHT ARCHITECTURE- MAKES KTOR AN INTERESTING OPTION FOR CREATING RESTFUL SERVICES

Slide 16

Slide 16 text

Module Module Module Application Routing Auth Content Negotiation Routing Content Negotiation F Logging F F F F F

Slide 17

Slide 17 text

Client Server Convert to ApplicationCall Setup Monitoring Features Call HTTP 1.x HTTP 2.x Websockets Fallback Pipeline of Interceptors

Slide 18

Slide 18 text

No content

Slide 19

Slide 19 text

Creating the application fun Application.lifeVisualiser() { //Module feature installations routing { //Module routing } } fun main() { embeddedServer(Netty, port = 8080, module = Application::lifeVisualiser ).start(wait = true) }

Slide 20

Slide 20 text

Setting up the routes (1) routing { get("/inspirations") { ... } get("/users/{userId}/inspirations") { ... } } install(ContentNegotiation) { gson { } }

Slide 21

Slide 21 text

Setting up the routes (2) get("/inspirations") { val keywords = call.parameters["keywords"] if (keywords == null) { call.respond(HttpStatusCode.BadRequest) return@get } } val inspirations = api.searchPhotosByKeywords(keywords) if (inspirations != null) { call.respond(HttpStatusCode.OK, inspirations) } else { call.respond(HttpStatusCode.NoContent) }

Slide 22

Slide 22 text

Route refinement - Locations get { val inspirations = inspirationApi.searchPhotosByKeywords(it.keywords) if (inspirations != null) { call.respond(HttpStatusCode.OK, inspirations) } else { call.respond(HttpStatusCode.NoContent) } } @Location("/inspirations") data class KeywordInspirations(val keywords: String) install(Locations)

Slide 23

Slide 23 text

Route refinement - Splitting into files MyRoute.kt fun Route.searchInspirationsByKeywords(inspirationApi: InspirationApi) = get { … } routing { searchInspirationsByKeywords(api) ... } Application.kt

Slide 24

Slide 24 text

Unsplash API Register as a developer Register the app to get access key and secret key The API section we’re using doesn’t require OAuth, just an Auth header with the access key Extensive documentation for API use

Slide 25

Slide 25 text

Setting up the Ktor client private val httpClient = HttpClient(CIO) { install(JsonFeature) { serializer = GsonSerializer { serializeNulls() disableHtmlEscaping() } } }

Slide 26

Slide 26 text

Using the Ktor client override suspend fun searchPhotosByKeywords(keywords: String) : List? { val url = "$unsplashUrl/search/photos?query=$keywords" return httpClient.get(url) { header("Authorization", "Client-ID $accessKey") }?.results?.mapNotNull { mapUnsplashToInspiration(it) } }

Slide 27

Slide 27 text

Is it beautiful? • First iteration, not so much • Following iterations: framework/ language provides means of refactoring away boilerplate and complexity • Minimal setup, even in code • Routing is easy to read and understand

Slide 28

Slide 28 text

4. DATA WITH EXPOSED

Slide 29

Slide 29 text

Exposed is a light-weight SQL library written for the Kotlin language Created and maintained by JetBrains DSL and DAO flavours Supports many databases

Slide 30

Slide 30 text

Exposed setup (1) dependencies { compile "com.h2database:h2:1.4.200" compile "org.jetbrains.exposed:exposed:0.14.1" compile “com.zaxxer:HikariCP:3.4.1" ... }

Slide 31

Slide 31 text

Exposed setup (2) init { database = Database.connect(hikari()) transaction (database) { create(Users) create(Inspirations) create(Tags) } }

Slide 32

Slide 32 text

Creating a table object Inspirations : Table() { val id = varchar("id", 128).primaryKey() val userId = varchar("userId", 128) references Users.id val photoUrl = varchar("photoUrl", 1024) val title = varchar("title", 100) val description = varchar("description", 1000) val addedDate = long("addedDate") }

Slide 33

Slide 33 text

A clever little piece of Kotlin code private suspend fun dbTransaction(block: () -> T): T { return withContext(Dispatchers.IO) { transaction(database) { block() } } }

Slide 34

Slide 34 text

Inserting data dbTransaction { Inspirations.insert { it[id] = UUID.randomUUID().toString() it[Inspirations.userId] = userId it[photoUrl] = photo.photoURL it[title] = photo.title it[description] = photo.description it[addedDate] = photo.addedDate } }

Slide 35

Slide 35 text

Querying data Inspirations.select { Inspirations.id eq id }.mapNotNull { }.firstOrNull() Inspiration( id = it[Inspirations.id], userId = it[Inspirations.userId], photoURL = it[Inspirations.photoUrl], title = it[Inspirations.title], description = it[Inspirations.description], tags = syncFindTagsByInspirationId(it[Inspirations.id]) ?: emptyList(), addedDate = it[Inspirations.addedDate] )

Slide 36

Slide 36 text

Is it beautiful? • Follows SQL closely • Use of this and it context objects • Select query syntax is concise and expressive

Slide 37

Slide 37 text

5. FRONTEND WITH KOTLINX.HTML

Slide 38

Slide 38 text

Parent Component Child Component Properties flow down Events flow up

Slide 39

Slide 39 text

Project setup with npm Install the project creator
 npm install -g create-react-kotlin-app Create the project
 npx create-react-kotlin-app lifevisualiser Run in the browser (live reloading) 
 npm start npm start

Slide 40

Slide 40 text

Creating a component step-by-step 1. Define a properties interface (Optional) 2. Define a state interface (Optional) 3. Create a component templated with the interfaces 4. Implement the render method (with kotlinx.html DSL) 5. Create a builder

Slide 41

Slide 41 text

Defining the properties interface class PhotoSelectionProps : RProps { lateinit var callback: ((Inspiration) -> Unit) }

Slide 42

Slide 42 text

Defining the state interface class PhotoSelectionState : RState { var inspirations = mutableListOf() }

Slide 43

Slide 43 text

Creating the component class PhotoSelectionComponent() : RComponent() { override fun RBuilder.render() { //Create the HTML } //Other lifecycle functions //Other functions }

Slide 44

Slide 44 text

Implementing the render method state.inspirations.forEach { inspiration -> div("mdc-layout-grid__cell--span-4") { div("mdc-card") { img(src = inspiration.photoURL, classes = "photo-selection-photo") { attrs.height = "200px" attrs.onClickFunction = { props.callback.invoke(inspiration) } } p { +inspiration.title } override fun RBuilder.render() { div("mdc-typography--headline1 inspiration-header") { +"LifeVisualiser" } div("mdc-card main-card") { //Form with search component div("mdc-layout-grid") { div("mdc-layout-grid__inner") {

Slide 45

Slide 45 text

Implementing the builder fun RBuilder.photoSelector(callback: (app.Inspiration) -> Unit) : ReactElement { return child(PhotoSelectionComponent::class) { //Initialise props here attrs.callback = callback } }

Slide 46

Slide 46 text

Using a JavaScript library directly 1. Install the library 
 npm install axios —save 2. Create external functions and interfaces 3. Use like regular Kotlin

Slide 47

Slide 47 text

Creating the external functions and interfaces @JsModule("axios") external fun axios(config: AxiosConfigSettings): Promise> external interface AxiosConfigSettings { var url: String var method: String ... } external interface AxiosResponse { val data: T val status: Number ... }

Slide 48

Slide 48 text

Using the functions fun getPhotosByKeywords(keywords: String) { val callConfig: AxiosConfigSettings = jsObject { url = "${App.BASE_URL}/inspirations/?keywords=$keywords" method = "GET" timeout = 3000 } axios>(callConfig).then { if (it.status == 200) { val newState = PhotoSelectionState().apply { inspirations.addAll(it.data) } setState(newState) } } }

Slide 49

Slide 49 text

Is it beautiful? • Follows HTML closely • Builder-like functions to construct the DOM

Slide 50

Slide 50 text

THANK YOU NGIYABONGA KE A LEBOGA NDZA NKHENSA KE ITUMETSE NDO LIVHUWA ENKOSI THANK YOU NGIYABONGA KE A LEBOGA NDZA NKHENSA KE ITUMETSE NDO LIVHUWA ENKOSI BAIE DANKIE THANK YOU NGIYABONGA KE A LEBOGA NDZA NKHENSA KE ITUMETSE NDO LIVHUWA ENKOSI THANK YOU NGIYABONGA KE A LEBOGA NDZA NKHENSA KE ITUMETSE NDO LIVHUWA ENKOSI

Slide 51

Slide 51 text

#KotlinConf THANK YOU AND REMEMBER TO VOTE Pamela Hill @pamelaahill