When you play a strategy game, how do you play. Do you hoard resources so you can rapidly build later. Hoard pointy sharp objects, so you can rule with an iron fist. Or some combination there of?

When starting with a multi platform project in Kotlin. Laying the ground work and providing a proper cohesive build system I feel is best. In this part we will be covering gradle, and wiring the build system together.

Deliverables

  • Set up scaffolding for the following.
    • React frontend written with material ui
    • Rest API with Swagger Support
    • Database abstraction with Jooq

What about KTS?

For those unaware kts. Is a a new way of writing kotlin files in a kotlin format. Further unifying how you write your code. I'm still wrapping my head around the new format. But will post an update.

Gradle Wrapper

It's always reccomended to run with a gradle wrapper.

gradle wrapper

Directory Layout

A kotlin multi platform project can feel like a go monolithic repo. Put another way it can feel very dense and claustorphobic. There is a lot going on in one repository. If not wired well it can become very cumbersome to manage. It really puts a stress on the terminology of full stack, as you will be working several different paradigms in one repo. This is going to be a personal preference, but this is the way I lay it out.

├── api
│   └── build.gradle
├── build
│   └── kotlin-build
│       └── version.txt
├── build.gradle
├── common
│   └── all
│       └── build.gradle
├── database
│   ├── build.gradle
│   └── secrets.sh
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── web
    └── build.gradle

settings.gradle

You can see the full details here. But in short this declares the hierachy of the project. Here we want to include all the different modules.

rootProject.name = 'KotlinMultiPlatformSkeleton'

include 'common/all'
include 'common/js'
include 'common/jvm'
include 'database'
include 'api'
include 'web'
include 'android'

When you use this skeleton.. Update the rootProject.name, and any include statements as you adjust components. This can be condensed to a comma seperated single line but I prefer the muli line format.

Root build.gradle

The root build.gradle is the basis for your projects. It will route the common versions together from one file. As a specific example the Kotlin version. When setting this in one top level gradle file it will then propegate down to all your sub projects.

Repositories

The following will set your repositories to be used. These are the various places it will go and look for dependencies.

    repositories {
        maven { url 'http://dl.bintray.com/kotlin/kotlin-eap-1.2' }
        maven { url 'https://dl.bintray.com/kotlin/kotlin-js-wrappers'}
        maven { url "https://kotlin.bintray.com/kotlinx" }
        maven { url 'https://maven.google.com' }
        maven { url "https://plugins.gradle.org/m2/" }
        maven { url "http://dl.bintray.com/kotlin/kotlin-dev" }
        maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }
        maven { url 'https://maven.fabric.io/public' }
        jcenter()
        mavenCentral()
        mavenLocal()
        google()
    }

BuildScript

The following segments are enclosed in a build script block

    buildscript {
       $Magical Care Bear Unicorns Go Here$
    }

Next we specify versions for each of these projects. I generally see two schools of thoughts with this. One is to specify all versions in a single file. Making it very easy to get an over view of all versions used in the current project. The next is to include only the common versions, then put speicifc items in a sub build.gradle.

In the below snippet for the second approach. We would move the html and react into the web module. I generally don't like this because you need to open several files.

        ext.KotlinVersion = '1.2.41'
        ext.SerializationVersion = '0.5.0'
        ext.CoroutineVersion = '0.22.5'
        ext.ReactKotlinVersion = '16.3.1-pre.27-kotlin-1.2.30'
        ext.ReactRouterVersion = '4.2.2-pre.27-kotlin-1.2.30'
        ext.KotlinxHTMLVersion = '0.6.10'
        ext.JoobyVersion = '1.3.0'
        ext.GSONVersion = '2.8.5'
        ext.HikariCPVersion = '3.1.0'
        ext.JUNITVersion = '4.12'
        ext.JooqVersion = '3.10.7'
        ext.PostgreSQLVersion = '42.1.4'
        ext.KotlinFrontendPluginVersion = '0.0.30'
        ext.NPMReactVersion = "16.3.2"
        ext.NPMReactRouterDOM = '4.2.2'
        ext.NPMWebPackCLI = 'v2.0.12'

So what are we setting here?

  • KotlinVersion is the core kotlin version. You can see the latest release here.
  • SerializationVersion is a great serialization library, more on it later, but here is the latest revision.
  • Coroutine is the async approach for kotlin latest here.
  • ReactKotlinVersion - This effects both the dom and base so it's multipurpose. Download
  • ReactRouter - The router library Download .

Dependencies

This will set the common build dependencies across all projects. These will not contain platform specific modules. But contain the basic items that will be used across all platform targets. So we're not specifying JVM, JavaScript, etc.

Common components across all platforms is the gradle plugin, coroutine support, and serialization support.

        dependencies {
            classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$KotlinVersion"
            classpath "org.jetbrains.kotlinx:kotlinx-gradle-serialization-plugin:$SerializationVersion"
            classpath "org.jetbrains.kotlinx:kotlinx-coroutines-core:$CoroutineVersion"
        }

Final Root Build Gradle Result

allprojects {

    repositories {
        maven { url 'http://dl.bintray.com/kotlin/kotlin-eap-1.2' }
        maven { url 'https://dl.bintray.com/kotlin/kotlin-js-wrappers'}
        maven { url "https://kotlin.bintray.com/kotlinx" }
        maven { url 'https://maven.google.com' }
        maven { url "https://plugins.gradle.org/m2/" }
        maven { url "http://dl.bintray.com/kotlin/kotlin-dev" }
        maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }
        maven { url 'https://maven.fabric.io/public' }
        jcenter()
        mavenCentral()
        mavenLocal()
        google()
    }

    buildscript {
        ext.KotlinVersion = '1.2.41'
        ext.SerializationVersion = '0.5.0'
        ext.CoroutineVersion = '0.22.5'
        ext.ReactKotlinVersion = '16.3.1-pre.27-kotlin-1.2.30'
        ext.ReactRouterVersion = '4.2.2-pre.27-kotlin-1.2.30'
        ext.KotlinxHTMLVersion = '0.6.10'

        repositories {
            maven { url 'http://dl.bintray.com/kotlin/kotlin-eap-1.2' }
            maven { url 'https://dl.bintray.com/kotlin/kotlin-js-wrappers'}
            maven { url "https://kotlin.bintray.com/kotlinx" }
            maven { url 'https://maven.google.com' }
            maven { url "https://plugins.gradle.org/m2/" }
            maven { url "http://dl.bintray.com/kotlin/kotlin-dev" }
            maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }
            maven { url 'https://maven.fabric.io/public' }
            jcenter()
            mavenCentral()
            mavenLocal()
            google()
        }

        dependencies {
            classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$KotlinVersion"
            classpath "org.jetbrains.kotlinx:kotlinx-gradle-serialization-plugin:$SerializationVersion"
            classpath "org.jetbrains.kotlinx:kotlinx-coroutines-core:$CoroutineVersion"
        }
    }

    repositories {
        maven { url 'http://dl.bintray.com/kotlin/kotlin-eap-1.2' }
        maven { url 'https://dl.bintray.com/kotlin/kotlin-js-wrappers'}
        maven { url "https://kotlin.bintray.com/kotlinx" }
        maven { url 'https://maven.google.com' }
        maven { url 'https://jitpack.io' }
        maven { url "http://dl.bintray.com/kotlin/kotlin-dev" }
        maven { url "http://dl.bintray.com/kotlinx/kotlinx" }
        maven { url "http://dl.bintray.com/hypnosphi/kotlin-wrappers" }
        mavenCentral()
        jcenter()
    }
    
    sourceSets {
        main.kotlin.srcDirs += "src/main/kotlin"
        test.kotlin.srcDirs += "src/test/kotlin"
    }
}

Common Build Gradle

In the multi platform parlance. A common module is language agnostic. A primary example is API responses, or data classes. Let's say we have a user object that an API responds with. That would be in common then we can inherit it in each respective sub project.

In addition to the above we can specify a base class. In object orientated parlance best viewed as an interface or abstract class. Then have actual implemetation in language specific sub projects.

Why would you do this? Let's say that User response has an id of a UUID. In the javascript react frontend it's fine as a string. But in the JVM/backend it would be better to cast as a UUID type.

So we generally see

  • common
  • common-js
  • common-java

I don't like clutter, and that clutters the root level to me. So I do the following.

  • common/all
  • common/java
  • common/js

For now I will just be populating all. The others will come into play much later.

build.gradle

apply plugin: 'kotlin-platform-common'
apply plugin: 'kotlinx-serialization'

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib-common:$KotlinVersion"
    compile "org.jetbrains.kotlinx:kotlinx-serialization-runtime-common:$SerializationVersion"
    compile "org.jetbrains.kotlinx:kotlinx-serialization-runtime:$SerializationVersion"
    testCompile "org.jetbrains.kotlin:kotlin-test-annotations-common:$KotlinVersion"
    testCompile "org.jetbrains.kotlin:kotlin-test-common:$KotlinVersion"
}

By declaring a lot of common logic in the root build.gradle. The sub projects become much smaller and easier to parse. So here we're applying two plugins.

  • Kotlin platform is common, described here. Telling the compiler that it will emit straight kotlin code consumable by all projects.
  • Kotlin Serialization adds a lot of code generation for casting those data classes to various formats.

Dependencies are rather simple as well. We are just using the common library and standard library.

Database build.gradle

We will be connecting a postgresql backend database. There are a variet of ORMs out there. Exposed seems to be the path forward. I use Jooq, for one big feature.

This gradle is the source code for your ORM layer. Something I have not worked out yet, is always rebuilding. During development if you make a schema change, and the source files are already present. It will not rebuild the database files. But just clear out source main java.

In an over arching hand wavey way, it's connecting to your database. Analyzing the schema definition. Then building out the corresponding java classes to connect and utilize the database. We will be building a single helper function for this as well.

The Jooq code generation takes in an xml configuration file. So we are programatically building that out. This makes it much more dynamic.

Complex

import javax.xml.bind.JAXB

apply plugin 'java'
apply plugin: 'kotlin'
apply plugin: 'maven'
version '1.0-SNAPSHOT'

def DBHOST = System.getenv("db_host")
def DBUSER = System.getenv("db_user")
def DBPASSWORD = System.getenv("db_password")
def DBPORT = System.getenv("db_port")
def DBURL = "jdbc:postgresql://${DBHOST}:${DBPORT}/kotlinmultiplatformskeleton"

dependencies {
    compile "org.jooq:jooq:$JooqVerison"
    runtime group: 'org.postgresql', name: 'postgresql', version: "$PostgreSQLVersion"
    testCompile "junit:junit:$JUNITVersion"
}

buildscript {
    dependencies {
        classpath 'org.jooq:jooq-codegen:$JooqVersion'
        classpath group: 'org.postgresql', name: 'postgresql', version: "$PostgreSQLVersion"
    }
}

def writer = new StringWriter()
def xml = new groovy.xml.MarkupBuilder(writer)
        .configuration('xmlns': 'http://www.jooq.org/xsd/jooq-codegen-3.10.0.xsd') {
    jdbc() {
        driver('org.postgresql.Driver')
        url(DBURL)
        user(DBUSER)
        password(DBPASSWORD)
        schema("public")
    }
    generator() {
        database() {
            inputSchema("public")
        }
        generate([:]) {
            pojos true
            daos true
        }
        target() {
            packageName('animus.design.kotlinmultiplatformskeleton.database')
            directory('${buildDir}/../src/main/java')
        }
    }
}

org.jooq.util.GenerationTool.generate(
        JAXB.unmarshal(new StringReader(writer.toString()), org.jooq.util.jaxb.Configuration.class)
)

Environment Variables

def DBHOST = System.getenv("db_host")
def DBUSER = System.getenv("db_user")
def DBPASSWORD = System.getenv("db_password")
def DBPORT = System.getenv("db_port")
def DBURL = "jdbc:postgresql://${DBHOST}:${DBPORT}/kotlinmultiplatformskeleton"

Much much later this will become apparent. We are dynamically pulling the database connection information from environment variables. This is helpful in your build chain. We can have a build job for a dev instance of the database, prod etc.

API build.gradle

The API will be providing a rest api and graphql endpoint. This is a skeleton project and my choice may not match with everyone else. So use what you see fit. But I'm using Jooby. There are more Rest frameworks out there, like...

  • Ktor Seems like the future, and is primarily if not all kotlin, and written to be asynchronous.
  • Spark
  • Javalin
  • Spring Boot
  • and many more

build.gradle

buildscript {
    dependencies {
        classpath "com.github.jengelman.gradle.plugins:shadow:2.0.1"
    }
}
apply plugin: 'kotlin-platform-jvm'
apply plugin: 'application'
apply plugin: "com.github.johnrengelman.shadow"



dependencies {
    expectedBy project(":common/all")
    compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$KotlinVersion"
    compile "com.google.code.gson:gson:$GSONVersion"
    compile group: 'org.jooby', name: 'jooby', version: "$JoobyVersion"
    compile group: 'org.jooby', name: 'jooby-jackson', version: "$JoobyVersion"
    compile group: 'org.jooby', name: 'jooby-spec', version: "$JoobyVersion"
    compile group: 'org.jooby', name: 'jooby-netty', version: "$JoobyVersion"
    compile "org.jooby:jooby-lang-kotlin:$JoobyVersion"
    compile "org.jooby:jooby-apitool:$JoobyVersion"
    compile group: 'com.zaxxer', name: 'HikariCP', version: "" $HikariCPVersion "
    testCompile "junit:junit:$JUNITVersion"
    testCompile "org.jetbrains.kotlin:kotlin-test-junit:$KotlinVersion"
    testCompile "org.jetbrains.kotlin:kotlin-test:$KotlinVersion"
}

mainClassName = 'animus.design.kotlinmultiplatform.api.DirectorKt'

jar {
    manifest {
        attributes(
                'Main-Class': 'animus.design.kotlinmultiplatform.api.DirectorKt'
        )
    }
}


sourceSets {
    main.kotlin.srcDirs += "src/main/kotlin"
    test.kotlin.srcDirs += "src/test/kotlin"
}


compileKotlin {
    kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
    kotlinOptions.jvmTarget = "1.8"
}
sourceCompatibility = "1.8"

shadowJar {
    baseName = 'KotlinMultiPlatformSkeleton.api'
    classifier = null
    version = version
}

Build Scipt & Plugins

buildscript {
    dependencies {
        classpath "com.github.jengelman.gradle.plugins:shadow:2.0.1"
    }
}
apply plugin: 'kotlin-platform-jvm'
apply plugin: 'application'
apply plugin: "com.github.johnrengelman.shadow"
  • Specify this will be built for the JVM platform.
  • It can be run as an application via gradle :api:run
  • We can build a fat jar for deployment, it's our only new build dependency ^.^.

Dependencies

This is just the plumbing pulling in all the libraries we use to build out our api.

Java Jar Configuration

mainClassName = 'animus.design.kotlinmultiplatform.api.DirectorKt'

jar {
    manifest {
        attributes(
                'Main-Class': 'animus.design.kotlinmultiplatform.api.DirectorKt'
        )
    }
}

shadowJar {
    baseName = 'KotlinMultiPlatformSkeleton.api'
    classifier = null
    version = version
}

This will allow us to run

java -jar KotlinMultiPlatformSkeletonAPI.jar

Accessing our main function, and launching the api. Conversely it also works for gradle run. Telling our application how to start.

Compiler Configuration

compileKotlin {
    kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
    kotlinOptions.jvmTarget = "1.8"
}
sourceCompatibility = "1.8"

Here we specify the target version of the JVM we hit. I'm still migrating over to JDK 10, and will be posting updates shortly.

Web Build Gradle

While there is create-react-kotlin-app. I found it easier to do it this way. This will be using gradle to dynamically build out a gradle plugin. We will be using kotlin frontend plugin. I don't know why but feel like this was the most convoluted portion I've worked on.

Deeper

group "animus.design"
version "1.0-SNAPSHOT"

buildscript {
    dependencies {
        classpath "org.jetbrains.kotlinx:kotlinx-gradle-serialization-plugin:$SerializationVersion"
        classpath "org.jetbrains.kotlin:kotlin-frontend-plugin:$KotlinFrontendPluginVersion"
    }
}

apply plugin: 'org.jetbrains.kotlin.frontend'
apply plugin: 'kotlin-platform-js'
apply plugin: 'kotlinx-serialization'

kotlinFrontend {
    downloadNodeJsVersion = 'latest'

    npm {
        dependency("webpack-cli", "v2.0.12")
        dependency("react", "16.3.2")
        dependency("react-dom", "16.3.2")
        dependency("react-router-dom", "4.2.2")
        dependency("@jetbrains/kotlin-react-router-dom")
        dependency("@jetbrains/kotlin-react-dom")
        dependency("@material-ui/core")
        dependency("@material-ui/icons")
    }

    sourceMaps = true

    webpackBundle {
        bundleName = "main"
        contentPath = file('src/main/web')
        proxyUrl = "http://localhost:3001"
        sourceMapEnabled = true
    }
}

dependencies {
    expectedBy project(':common')
    compile "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version"
    compile "org.jetbrains:kotlin-react:$ReactKotlinVersion"
    compile "org.jetbrains:kotlin-react-dom:$ReactKotlinVersion"
    compile "org.jetbrains:kotlin-react-router-dom:$ReactRouterVersion"
    compile "org.jetbrains.kotlinx:kotlinx-serialization-runtime-js:$SerializationVersion"
    compile "org.jetbrains.kotlin:kotlin-stdlib-js:$KotlinVersion"
    compile "org.jetbrains.kotlinx:kotlinx-html-js:$KotlinxHTMLVersion"
    compile "org.jetbrains.kotlinx:kotlinx-serialization-runtime:$SerializationVersion"
}

compileKotlin2Js {
    kotlinOptions.metaInfo = true
    kotlinOptions.outputFile = "$project.buildDir.path/js/${project.name}.js"
    kotlinOptions.sourceMap = true
    kotlinOptions.moduleKind = 'commonjs'
    kotlinOptions.main = "call"
}

kotlin {
    experimental {
        coroutines "enable"
    }
}

Build Script Configuration

buildscript {
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-frontend-plugin:$KotlinFrontendPluginVersion"
    }
}

apply plugin: 'org.jetbrains.kotlin.frontend'
apply plugin: 'kotlin-platform-js'
apply plugin: 'kotlinx-serialization'
  • Specifiy and use kotlin frontend for webpack UI.
  • Build for the kotlin javascript platform
  • Utilize serialization.

Dependencies

One of the biggest caveats with kotlin and js. Is the onion or layered approach of dependencies. When a new version of react hits, we need to wait for the kotlin bindings to reflect that new version. So we are ensuring that the react version matches the kotlin binding versions.

This also brings to bare the facet that we are specifying two depdencies. Kotlin is being compiled to javascript. Much like typescript compiles to javascript. However we are not in the same ecosystem with type bindings. We are in a sister ecosystem that has it's own type system. So we need depdencies that kotlin understands. This breaks down to node vs gradle.

Maybe this helps.

Kotlinc -> Webpack -> Bundle JS -> Node -> Running Server

I think it's right, that's my understanding. I'm going to deep dive into this here. Which is outside of the gradle definition stuff. But I went through several french presses while wrapping my head around this.

User interface is currentlly using material ui components. I will be posting bindings for those very soon.

Gradle
dependencies {
    expectedBy project(':common/all')
    compile "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version"
    compile "org.jetbrains:kotlin-react:$ReactKotlinVersion"
    compile "org.jetbrains:kotlin-react-dom:$ReactKotlinVersion"
    compile "org.jetbrains:kotlin-react-router-dom:$ReactRouterVersion"
    compile "org.jetbrains.kotlinx:kotlinx-serialization-runtime-js:$SerializationVersion"
    compile "org.jetbrains.kotlin:kotlin-stdlib-js:$KotlinVersion"
    compile "org.jetbrains.kotlinx:kotlinx-html-js:$KotlinxHTMLVersion"
    compile "org.jetbrains.kotlinx:kotlinx-serialization-runtime:$SerializationVersion"
}

When we call kotlinc. It needs to understand how the typing of Javascript works. We utilize kotlin files that specify classes and interfaces with type definitions. So let's say we import

import react.RComponent

This is not importing a javascript file it's importing a kotlin file. With definitions for the React Component class. which itself loads from the java script. Just ignore the man behind the curtain, you're in the warmth of a nice type system.

NPM
    npm {
        dependency("webpack-cli", "$NPMWebPackCLI")
        dependency("react", "$NPMReactVersion")
        dependency("react-dom", "$NPMReactVersion")
        dependency("react-router-dom", "$NPMReactRouterDOM")
        dependency("@jetbrains/kotlin-react-router-dom")
        dependency("@jetbrains/kotlin-react-dom")
        dependency("@material-ui/core")
        dependency("@material-ui/icons")
    }

So these will pull into build/node_modules. Which with webpack will be minified down to a main bundle js file. As to what we're using. For this quick setup we're utilizing material-ui. But you can use styled components if you know the CSS.

Finishing Up

So with this you should now have build files for your project and each module. I also hope you have a bit of an understanding about why we selected each component.

Link to Git repository tag of part 1.