Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Kotlin, Kotlin, Everywhere...

pahill
March 28, 2019

Kotlin, Kotlin, Everywhere...

Many developers are under the mistaken impression that Kotlin is just for the Android platform. However, more and more developers are starting to use Kotlin for web backends and even frontends.

This talk will touch on the most popular language features of Kotlin. However, the bulk of the talk will consist of demonstrating that Kotlin can and should also be used for full-stack web development. I will take the audience through the development of a web app using Kotlin/JVM on the backend and Kotlin/JS on the frontend, using Spring Boot and React respectively.

pahill

March 28, 2019
Tweet

More Decks by pahill

Other Decks in Programming

Transcript

  1. I am Pamela Hill Android Engineer - Luno You can

    find me here: ‒ Twitter: @pamelaahill ‒ Blog: www.pamelaahill.com Hello! ! 2
  2. Contents ‒ Why Kotlin? ‒ Project introduction ‒ Backend with

    Spring Boot ‒ Alternative backend with Ktor ‒ Frontend with React ‒ Cool experiment, but… ! 3
  3. Why Kotlin? ‒ Supports more comprehensible code ‒ Supports safer

    code ‒ Allows different programming paradigms ‒ Fully interoperable with Java ! 5
  4. Full-Stack Project Our goal: to create a full-stack Kotlin web

    app that generates absurd inspirational quotes. ! 8
  5. ! 13 ‒ To get a new inspiration:
 POST /users/{{user_id}}/inspirations

    ‒ To get an inspiration image:
 GET /users/{{user_id}}/inspirations/{{inspiration_id}}/ images ‒ To change the tags for an inspiration:
 PUT /users/{{user_id}}/inspirations/{{inspiration_id}} ‒ To find a tag by title:
 GET /tags?title=anytitle ‒ To find inspirations by tag:
 GET /users/{{user_id}}/inspirations?tagId=tag_id
  6. What is Spring Boot? ‒ Simplifies building services with Spring

    ‒ Easy setup, easy deploy ‒ Easy data access with JPA through Spring Data ‒ Support of Spring ecosystem ‒ First-class support of Kotlin ! 15
  7. Steps to create the project 1. Generate the project with

    Spring Initializr 2. Application 3. Entities - representation of data and tables 4. Repositories - CRUD operations on database 5. Rest Controllers - handle HTTP requests ! 17
  8. ! 21 @Entity @Table(name = "inspirations") data class Inspiration( val

    userId: Long, val fileURL: URL, @ManyToMany(...) @JoinTable( name = "post_tags", joinColumns = [JoinColumn(name = "post_id")], inverseJoinColumns = [JoinColumn(name = "tag_id")] ) var tags: List<Tag> = mutableListOf(), val generateDate: Long = Date().time ) { ... }
  9. ! 22 data class Inspiration( ... ) { @Id @GeneratedValue

    var id: Long = 0L override fun equals(other: Any?): Boolean { //id based equality } override fun hashCode(): Int { //id based hash code } }
  10. ! 23 interface InspirationRepository : CrudRepository<Inspiration, Long> { fun findByUserId(userId:

    Long): List<Inspiration> fun findByTags(tag: Tag): List<Inspiration> } interface TagRepository : CrudRepository<Tag, Long> { fun findByTitle(title: String): List<Tag> }
  11. ! 24 @RestController @RequestMapping("/api/users/{userId}") class InspirationController( private val inspirationRepository: InspirationRepository,

    private val imageRepository: ImageRepository, private val tagRepository: TagRepository ) { ... }
  12. ! 25 @PostMapping("/inspirations") fun create(@PathVariable("userId", required = true) userId: Long)

    : Inspiration? { val inspirobotURL = getInspirobotURL() ?: return null val inspirobotImage = getInspirobotImage(inspirobotURL) ?: return null val fileURL = imageRepository.save( inspirobotURL.substringAfterLast("."), inspirobotImage.inputStream()) return inspirationRepository.save(Inspiration(userId, fileURL)) }
  13. What is Ktor? ‒ Kotlin framework for building asynchronous clients

    and servers ‒ Built on coroutines ‒ Use DSL to specify application behaviour ‒ Easy setup using IntelliJ Plugin ‒ Production-ready ‒ ThoughtWorks Technology Radar - Assess category ! 28
  14. “ The flexibility to incorporate different tools for logging, DI

    or a templates engine—in addition to its lightweight architecture—makes Ktor an interesting option for our teams for creating RESTful services. - ThoughtWorks ! 29
  15. Application HTTP 1.x HTTP 2.x Websockets … Convert to ApplicationCall

    Interceptor Interceptor Interceptor Interceptor Client Pipeline
  16. ! 32 fun main() { embeddedServer( Netty, port = 8080,

    module = Application::inspirobot ).start(wait = true) }
  17. ! 33 fun Application.inspirobot() { val db: InspirationDb = InspirationDbImpl()

    val api: InspirationApi = InspirationApiImpl() install(ContentNegotiation) { gson { } } routing { ... } }
  18. ! 34 routing { post("/users/{userId}/inspirations") { //Get and check userId

    //Create the inspiration //Save the inspiration //Return the inspiration } }
  19. ! 35 ... //Get and check the userId val userId

    = call.parameters["userId"]?.toLongOrNull() if (userId == null) { call.respond(HttpStatusCode.BadRequest) return@post } ...
  20. ! 36 ... //Create the inspiration val inspirationUrl = api.getInspiration()

    if (inspirationUrl == null) { call.respond(HttpStatusCode.InternalServerError) return@post } ...
  21. ! 37 ... //Save the inspiration val inspiration = db.createInspiration(

    userId = userId, fileExtension = inspirationUrl.toExternalForm() .substringAfterLast("."), inputStream = api.getInspirationImage(inspirationUrl) ) if (inspiration == null) { call.respond(HttpStatusCode.InternalServerError) return@post } ...
  22. ! 39 override suspend fun getInspiration(): URL? { val httpClient

    = HttpClient(CIO) val url = httpClient.get<String?>(INSPIROBOT_URL) return if (url != null) { URL(url) } else { null } }
  23. Database access ‒ Exposed: API for relational databases ‒ H2:

    in-memory database ‒ HikariCP: connection pooling ! 40
  24. ! 41 override suspend fun createInspiration( userId: Long, fileExtension: String,

    inputStream: ByteArrayInputStream ): Inspiration? { return dbQuery { //Save the file //Insert the inspiration //Find and return the inspiration } }
  25. ! 42 //Insert the inspiration val id = Inspirations.insert {

    it[Inspirations.userId] = userId it[Inspirations.fileUrl] = inspirationImage .toURI().toURL().toExternalForm() it[Inspirations.generatedDate] = Date().time } get Inspirations.id
  26. ! 43 //Find and return the inspiration id?.let { id

    -> Inspirations.select { Inspirations.id eq id }.mapNotNull { mapInspirationsToInspiration(it) }.firstOrNull() }
  27. What is React? ‒ Javascript framework for building UIs ‒

    Compose UIs from small components ‒ Alternative to Angular and Vue.js ‒ Kotlin/JS React wrapper libraries ! 46
  28. Components in React ! 47 App Chip ChipEditor Inspiration Home

    Chip Inspiration Search Properties flow down Events flow up
  29. Setup ‒ Install the project creator
 npm install -g create-react-kotlin-app

    ‒ Create the project
 npx create-react-kotlin-app inspirobot ‒ Run (live-loading) in the browser
 npm start ! 48
  30. Creating a component 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 ! 49
  31. ! 50 interface ChipProps : RProps { var text: String

    var allowClose: Boolean var callback: (() -> Unit)? }
  32. ! 52 override fun RBuilder.render() { div("chip fade show") {

    +props.text if(props.allowClose) { button(classes = "close", type = ButtonType.button) { attrs { onClickFunction = { props.callback?.invoke() } } i(classes = "material-icons") { +"cancel" } } } } }
  33. ! 53 fun RBuilder.chip(text: String, allowClose: Boolean = false, callback:

    (() -> Unit)? = null ) = child(ChipComponent::class) { attrs.text = text attrs.allowClose = allowClose attrs.callback = callback }
  34. Using a new JavaScript library ‒ Install the library
 npm

    install axios --save ‒ Create external functions and interfaces ‒ Treat almost like normal Kotlin code ! 54
  35. ! 55 @JsModule("axios") external fun <T> axios(config: AxiosConfigSettings): Promise<AxiosResponse<T>> external

    interface AxiosConfigSettings { var url: String var method: String var timeout: Number ... } external interface AxiosResponse<T> { val data: T val status: Number ... }
  36. ! 56 private fun createInspiration(callback: (Inspiration) -> Unit) { val

    callConfig: AxiosConfigSettings = jsObject { url = "$BASE_URL/users/1/inspirations" method = "POST" timeout = 3000 } axios<Inspiration>(callConfig).then { callback(it.data) } }
  37. Documentation / Setup Documentation / tutorials / help: ‒ Kotlin/JS

    documentation is a bit scarce ‒ Documentation on Ktor & Spring Boot is much better ‒ Lively Slack / Twitter community Setup: ‒ Kotlin/JS through npm module ‒ Ktor & Spring Boot through IntelliJ IDEA ! 59
  38. Skill / Effort Skills required: ‒ Kotlin basics, using DSLs,

    a little coroutines ‒ Intermediate level and above, or learn quickly Effort required: ‒ Spring Boot took the least time ‒ Kotlin/JS took the most time ! 60
  39. You can find me here: ‒ Twitter: @pamelaahill ‒ Blog:

    www.pamelaahill.com ‒ GitHub: https://github.com/pahill Thanks! ! 62