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

Full Stack Kotlin

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

Full Stack Kotlin

Avatar for Guillermo Orellana

Guillermo Orellana

February 12, 2019
Tweet

More Decks by Guillermo Orellana

Other Decks in Programming

Transcript

  1. + =

  2. Web

  3. npm { dependency "react", "16.3.1" dependency "react-dom" dependency "react-router" dependency

    "react-router-dom" devDependency "babel-loader" devDependency "babel-core" devDependency "css-loader" devDependency "style-loader" devDependency "source-map-loader" }
  4. webpackBundle { publicPath = "/frontend/" port = 8080 proxyUrl =

    "http: //localhost:9090/" sourceMapEnabled = true stats = "errors-only" }
  5. ./gradlew web:run > Task :web:nodejs-download UP-TO-DATE > Task :web:npm-preunpack UP-TO-DATE

    > Task :web:npm-configure > Task :web:npm-install UP-TO-DATE > Task :web:npm-index UP-TO-DATE > Task :web:npm-deps > Task :web:npm > Task :web:packages > Task :web:compileKotlin2Js > Task :web:compileJava NO-SOURCE > Task :web:processResources UP-TO-DATE > Task :web:classes > Task :web:compileTestKotlin2Js NO-SOURCE > Task :web:webpack-config UP-TO-DATE > Task :web:karma-config SKIPPED > Task :web:karma-start SKIPPED > Task :web:processTestResources NO-SOURCE > Task :web:runDceKotlinJs > Task :web:runDceTestKotlinJs NO-SOURCE > Task :web:webpack-run webpack started, see http: //localhost:8080/ > Task :web:run BUILD SUCCESSFUL in 9s 15 actionable tasks: 5 executed, 10 up-to-date ./gradlew web:run
  6. const Submission = (props) => ( <div className={"col-md-4"}> <h4>{props.submission.title} </h4>

    <p>{props.submission.abstract} </p> <Link to={`${props.submission.submissionUrl}`} className={"btn btn-secondary btn-lg"}> {"View details"} </Link> </div> ); // Usage <Submission submission={{…}} />
  7. fun fetchUserProfileFromId(userId: String) = promise { val user = userProfile(userId)

    setState { userProfile = user } } .catch { console.error(it) }
  8. fun main(args: Array<String>) { embeddedServer(Netty, 8080) { routing { get("/")

    { call.respondText("My Example Blog", ContentType.Text.Html) } } }.start(wait = true) } http://ktor.io/
  9. class ApplicationPage : Template<HTML> { val caption = Placeholder<TITLE>() val

    head = Placeholder<HEAD>() override fun HTML.apply() { head { meta { charset = "utf-8" } meta { name = "viewport" content = "width=device-width, initial-scale=1.0" } title { insert(caption) } insert(head) } body { div { id = "content" } script(src = "frontend/frontend.bundle.js") } } }
  10. class KeynotedexPageContent : Template<HTML> { val head = Placeholder<HEAD>() val

    bundle = Placeholder<SCRIPT>() override fun HTML.apply() { head { meta { charset = "utf-8" } meta { name = "viewport" content = "width=device-width, initial-scale=1.0, shrink-to-fit=no" } title { +"Keynotedex" } insert(head) link( rel = LinkRel.stylesheet, type = LinkType.textCss, href = "https: //maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" ) { attributes["integrity"] = "sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" attributes["crossorigin"] = "anonymous" } } body { div { id = "content" } script(src = "https: //code.jquery.com/jquery-3.2.1.slim.min.js") { attributes["integrity"] = "sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" attributes["crossorigin"] = "anonymous" } script(src = "https: //cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js") { attributes["integrity"] = "sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" attributes["crossorigin"] = "anonymous" } script(src = "https: //maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js") { attributes["integrity"] = "sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" attributes["crossorigin"] = "anonymous" } script { insert(bundle) } } } }
  11. fun Route.index() { static("frontend") { resource("web.bundle.js") } accept(ContentType.Text.Html) { get<IndexPage>

    { val model = mapOf( "jsbundle" to "/frontend/web.bundle.js" ) call.respond(FreeMarkerContent("index.ftl", model)) } } }
  12. @Test fun `when userId and password provided then user is

    created`() = testApp(mockStorage) { handleRequest(HttpMethod.Post, endpoint) { addHeader( HttpHeaders.ContentType, ContentType.Application.FormUrlEncoded.toString() ) setBody( listOf( "userId" to testId, "password" to testPassword ).formUrlEncode() ) }.apply { assertThat(response.status(), equalTo(HttpStatusCode.Created)) } }
  13. Platform specific code actual class Foo actual constructor(val bar: String)

    { actual fun frob() { println("Frobbing the $bar") } } Native Common expect class Foo(bar: String) { fun frob() }
  14. Counting objects: 155, done. Delta compression using up to 36

    threads. Compressing objects: 100% (50/50), done. Writing objects: 100% (89/89), 17.45 KiB | 0 bytes/s, done. Total 89 (delta 33), reused 75 (delta 19) remote: -----> Cleaning up ... remote: -----> Building keynotedex from herokuish ... remote: -----> Adding BUILD_ENV to build environment ... remote: -----> Gradle app detected remote: -----> Installing JDK 1.8 ... done remote: -----> Building Gradle app ... remote: -----> executing ./gradlew stage remote: To honour the JVM settings for this build a new JVM will be forked. Please consider using the daemon: https: //docs.gradle.org/4.10.2/userguide/gradle_daemon.html. remote: Daemon will be stopped at the end of the build stopping after processing remote: > Task :buildSrc:discoverMainScriptsExtensions remote: > Task :buildSrc:compileKotlin remote: > Task :buildSrc:compileJava NO-SOURCE remote: > Task :buildSrc:compileGroovy NO-SOURCE remote: > Task :buildSrc:processResources NO-SOURCE remote: > Task :buildSrc:classes UP-TO-DATE remote: > Task :buildSrc:inspectClassesForKotlinIC remote: > Task :buildSrc:jar remote: > Task :buildSrc:assemble remote: > Task :buildSrc:discoverTestScriptsExtensions remote: > Task :buildSrc:compileTestKotlin NO-SOURCE remote: > Task :buildSrc:compileTestJava NO-SOURCE remote: > Task :buildSrc:compileTestGroovy NO-SOURCE remote: > Task :buildSrc:processTestResources NO-SOURCE remote: > Task :buildSrc:testClasses UP-TO-DATE remote: > Task :buildSrc:test NO-SOURCE remote: > Task :buildSrc:check UP-TO-DATE remote: > Task :buildSrc:build remote: > Task :backend:clean UP-TO-DATE remote: > Task :backend:discoverMainScriptsExtensions remote: > Task :common:compileJava NO-SOURCE remote: > Task :common:compileKotlinCommon remote: > Task :common:processResources NO-SOURCE remote: > Task :common:classes remote: > Task :common:inspectClassesForKotlinIC remote: > Task :common:jar remote: > Task :common-jvm:discoverMainScriptsExtensions remote: > Task :common-jvm:compileKotlin remote: > Task :common-jvm:compileJava NO-SOURCE remote: > Task :common-jvm:processResources NO-SOURCE remote: > Task :common-jvm:classes UP-TO-DATE remote: > Task :common-jvm:inspectClassesForKotlinIC remote: > Task :common-jvm:jar remote: > Task :backend:compileKotlin remote: remote: w: /tmp/build/backend/src/main/kotlin/es/guillermoorellana/keynotedex/backend/Application.kt: (52, 13): This declaration is experimental and its usage should be marked with '@io.ktor.locations.KtorExperimentalLocationsAPI' or '@UseExperimental(io.ktor.locations.KtorExperimentalLocationsAPI ::class)' remote: w: /tmp/build/backend/src/main/kotlin/es/guillermoorellana/keynotedex/backend/Application.kt: (85, 9): This declaration is experimental and its usage should be marked with '@io.ktor.locations.KtorExperimentalLocationsAPI' or '@UseExperimental(io.ktor.locations.KtorExperimentalLocationsAPI ::class)'
  15. remote: "id" : "Keynotedex", remote: # application.conf @ jar:file:/app/backend/build/libs/backend-0.0.1-SNAPSHOT-release.jar!/application.conf: 13

    remote: "modules" : [ remote: # application.conf @ jar:file:/app/backend/build/libs/backend-0.0.1-SNAPSHOT-release.jar!/application.conf: 13 remote: "es.guillermoorellana.keynotedex.backend.ApplicationKt.keynotedex" remote: ] remote: }, remote: # application.conf @ jar:file:/app/backend/build/libs/backend-0.0.1-SNAPSHOT-release.jar!/application.conf: 2 remote: "deployment" : { remote: # application.conf @ jar:file:/app/backend/build/libs/backend-0.0.1-SNAPSHOT-release.jar!/application.conf: 7 remote: "autoreload" : true, remote: # env var ENVIRONMENT remote: "environment" : "production", remote: # env var PORT remote: "port" : "5000", remote: # application.conf @ jar:file:/app/backend/build/libs/backend-0.0.1-SNAPSHOT-release.jar!/application.conf: 8 remote: "watch" : [ remote: # application.conf @ jar:file:/app/backend/build/libs/backend-0.0.1-SNAPSHOT-release.jar!/application.conf: 8 remote: "keynotedex" remote: ] remote: }, remote: # Content hidden remote: "security" : " ***" remote: } remote: 2019-02-09 16:33:31.774 [main] DEBUG Keynotedex - Java Home: /app/.jdk remote: 2019-02-09 16:33:31.797 [main] DEBUG Keynotedex - Class Loader: sun.misc.Launcher$AppClassLoader@33909752: [/app/backend/build/libs/backend-0.0.1-SNAPSHOT-release.jar] remote: 2019-02-09 16:33:31.807 [main] INFO Keynotedex - No ktor.deployment.watch patterns match classpath entries, automatic reload is not active remote: 2019-02-09 16:33:34.270 [main] WARN Keynotedex - Populating db with mock data remote: 2019-02-09 16:33:35.991 [main] INFO Keynotedex - Responding at http: //0.0.0.0:5000 remote: 2019-02-09 16:33:35.993 [main] TRACE Keynotedex - Application started: io.ktor.application.Application@274872f8 remote: 2019-02-09 16:33:37.490 [nettyCallPool-4-1] TRACE Keynotedex - 200 OK: GET - / remote: 2019-02-09 16:33:37.576 [nettyCallPool-4-2] TRACE Keynotedex - 200 OK: GET - /logout remote: 2019-02-09 16:33:41.958 [nettyCallPool-4-3] TRACE Keynotedex - 200 OK: GET - /frontend/web.bundle.js remote: =====> end keynotedex web container output remote: -----> Running post-deploy remote: -----> Configuring keynotedex.wiyarmir.es ...(using built-in template) remote: -----> Creating https nginx.conf remote: -----> Running nginx-pre-reload remote: Reloading nginx remote: -----> Setting config vars remote: DOKKU_APP_RESTORE: 1 remote: -----> Found previous container(s) (5e0586566aa2) named keynotedex.web.1 remote: =====> Renaming container (5e0586566aa2) keynotedex.web.1 to keynotedex.web.1.1549730034 remote: =====> Renaming container (321dd6999ae1) cocky_ride to keynotedex.web.1 remote: -----> Attempting to run scripts.dokku.postdeploy from app.json (if defined) remote: -----> Shutting down old containers in 60 seconds remote: =====> 5e0586566aa204eb393e82496cb7d1a26d3f13471e81c95f559f01fb8788da22 remote: =====> Application deployed: remote: http: //keynotedex.wiyarmir.es remote: https: //keynotedex.wiyarmir.es remote: To [email protected]:keynotedex 8d2a091 ..abee039 master -> master
  16. deploy-backend: docker: - image: buildpack-deps:trusty steps: - checkout - run:

    name: Deploy Master to Heroku command: | ssh-keyscan -H dev.wiyarmir.es >> ~/.ssh/known_hosts git push $HEROKU_URL master
  17. Backend code Frontend code Kotlinx.serialization JAR JS bundle ktor React

    Data transfer objects https://commons.wikimedia.org/wiki/File:Google_Chrome_icon_(September_2014).svg https://github.com/dokku/dokku
  18. Backend code Frontend code Kotlinx.serialization JAR JS bundle ktor React

    Data transfer objects https://commons.wikimedia.org/wiki/File:Google_Chrome_icon_(September_2014).svg https://github.com/dokku/dokku http://www.quickmeme.com/p/3w2euz