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

Full Stack Kotlin

Full Stack Kotlin

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