Browse Source

Kooby (jooby + kotlin) (#4903)

Edgar Espina 6 years ago
parent
commit
eeca4343b2

+ 22 - 0
frameworks/Kotlin/kooby/.gitignore

@@ -0,0 +1,22 @@
+*.class
+
+# Package Files #
+*.jar
+*.war
+*.ear
+target
+.classpath
+.project
+.settings
+dependency-reduced-pom.xml
+bin
+
+# OSX
+*.DS_Store
+
+# IntelliJ IDE Project Files #
+*.iml
+*.idea
+*.versionsBackup
+pom.xml.versionsBackup
+jacoco.exec

+ 54 - 0
frameworks/Kotlin/kooby/README.md

@@ -0,0 +1,54 @@
+# Kooby (Jooby + Kotlin)
+
+[Jooby](https://jooby.io) the modular micro web framework for Java and Kotlin.
+
+```kt
+import io.jooby.runApp
+
+fun main(args: Array<String>) {
+  runApp(args) {
+
+    get("/") {
+      "Hello Kooby!"
+    }
+
+  }
+}
+
+```
+
+This is the [Jooby](https://jooby.io) portion of a [benchmarking test suite](../) comparing a variety of web development platforms.
+
+## Test URLs
+
+### Plain Text Test
+
+    http://localhost:8080/plaintext
+
+### JSON Encoding Test
+
+    http://localhost:8080/json
+
+### Single Query Test
+
+    http://localhost:8080/db
+
+### Multiple Queries Test
+
+    http://localhost:8080/queries
+
+### Database updates Test
+
+    http://localhost:8080/updates
+
+### Fortunes Test
+
+    http://localhost:8080/fortunes
+
+## build
+
+    mvn clean package
+    
+## run
+
+    java -jar target/kooby.jar

+ 28 - 0
frameworks/Kotlin/kooby/benchmark_config.json

@@ -0,0 +1,28 @@
+{
+  "framework": "kooby",
+  "tests": [{
+    "default": {
+      "json_url": "/json",
+      "plaintext_url": "/plaintext",
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      "fortune_url": "/fortunes",
+      "update_url": "/updates?queries=",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Fullstack",
+      "framework": "jooby for kotlin",
+      "language": "Kotlin",
+      "flavor": "None",
+      "platform": "Undertow",
+      "database": "Postgres",
+      "database_os": "Linux",
+      "orm": "Raw",
+      "webserver": "None",
+      "os": "Linux",
+      "notes": "Jooby with Kotlin and Undertow",
+      "display_name": "kooby",
+      "versus": "undertow"
+    }
+  }]
+}

+ 3 - 0
frameworks/Kotlin/kooby/conf/application.conf

@@ -0,0 +1,3 @@
+db.url = "jdbc:postgresql://tfb-database:5432/hello_world"
+db.user = benchmarkdbuser
+db.password = benchmarkdbpass

+ 12 - 0
frameworks/Kotlin/kooby/conf/logback.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration scan="true" scanPeriod="15 seconds" debug="false">
+  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+    <encoder>
+      <pattern>[%d{ISO8601}]-[%thread] %-5level %logger - %msg%n</pattern>
+    </encoder>
+  </appender>
+
+  <root level="INFO">
+    <appender-ref ref="STDOUT" />
+  </root>
+</configuration>

+ 12 - 0
frameworks/Kotlin/kooby/kooby.dockerfile

@@ -0,0 +1,12 @@
+FROM maven:3.6.1-jdk-11-slim as maven
+WORKDIR /kooby
+COPY pom.xml pom.xml
+COPY src src
+COPY public public
+RUN mvn package -q
+
+FROM openjdk:11.0.3-jdk-slim
+WORKDIR /kooby
+COPY --from=maven /kooby/target/kooby.jar app.jar
+COPY conf conf
+CMD ["java", "-server", "-Xms512m", "-Xmx2g", "-jar", "app.jar"]

+ 177 - 0
frameworks/Kotlin/kooby/pom.xml

@@ -0,0 +1,177 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>io.jooby</groupId>
+    <artifactId>jooby-project</artifactId>
+    <version>2.0.0</version>
+  </parent>
+
+  <artifactId>kooby</artifactId>
+  <groupId>com.techempower</groupId>
+  <version>2.0</version>
+
+  <name>kooby: jooby + kotlin</name>
+
+  <properties>
+    <jooby.version>2.0.0</jooby.version>
+    <postgresql.version>42.2.5</postgresql.version>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <maven.compiler.source>1.8</maven.compiler.source>
+    <maven.compiler.target>1.8</maven.compiler.target>
+
+    <!-- Startup class -->
+    <application.class>kooby.AppKt</application.class>
+    <kotlin.version>1.3.40</kotlin.version>
+  </properties>
+
+  <dependencies>
+    <!-- jackson -->
+    <dependency>
+      <groupId>io.jooby</groupId>
+      <artifactId>jooby-jackson</artifactId>
+    </dependency>
+
+    <!-- jdbc -->
+    <dependency>
+      <groupId>io.jooby</groupId>
+      <artifactId>jooby-hikari</artifactId>
+    </dependency>
+
+    <!-- rocker -->
+    <dependency>
+      <groupId>io.jooby</groupId>
+      <artifactId>jooby-rocker</artifactId>
+    </dependency>
+
+    <!-- mySQL -->
+    <dependency>
+      <groupId>mysql</groupId>
+      <artifactId>mysql-connector-java</artifactId>
+    </dependency>
+
+    <!-- postgresql -->
+    <dependency>
+      <groupId>org.postgresql</groupId>
+      <artifactId>postgresql</artifactId>
+      <version>${postgresql.version}</version>
+    </dependency>
+
+    <!-- logging -->
+    <dependency>
+      <groupId>ch.qos.logback</groupId>
+      <artifactId>logback-classic</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>io.jooby</groupId>
+      <artifactId>jooby-utow</artifactId>
+      <version>${jooby.version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.jetbrains.kotlin</groupId>
+      <artifactId>kotlin-stdlib-jdk8</artifactId>
+      <version>${kotlin.version}</version>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <finalName>kooby</finalName>
+    <sourceDirectory>src/main/kotlin</sourceDirectory>
+
+    <plugins>
+      <plugin>
+        <groupId>com.fizzed</groupId>
+        <artifactId>rocker-maven-plugin</artifactId>
+        <version>1.2.1</version>
+        <executions>
+          <execution>
+            <id>generate-rocker-templates</id>
+            <phase>generate-sources</phase>
+            <goals>
+              <goal>generate</goal>
+            </goals>
+            <configuration>
+              <templateDirectory>public</templateDirectory>
+              <touchFile>/dev/null</touchFile>
+              <optimize>true</optimize>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <artifactId>kotlin-maven-plugin</artifactId>
+        <version>${kotlin.version}</version>
+        <configuration>
+          <jvmTarget>1.8</jvmTarget>
+        </configuration>
+        <groupId>org.jetbrains.kotlin</groupId>
+        <executions>
+          <execution>
+            <id>compile</id>
+            <phase>compile</phase>
+            <goals>
+              <goal>compile</goal>
+            </goals>
+            <configuration>
+              <sourceDirs>
+                <source>target/generated-sources/rocker</source>
+                <source>src/main/kotlin</source>
+              </sourceDirs>
+            </configuration>
+          </execution>
+
+          <execution>
+            <id>test-compile</id>
+            <phase>test-compile</phase>
+            <goals>
+              <goal>test-compile</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>3.8.0</version>
+        <executions>
+          <!-- Replacing default-compile as it is treated specially by maven -->
+          <execution>
+            <id>default-compile</id>
+            <phase>none</phase>
+          </execution>
+          <!-- Replacing default-testCompile as it is treated specially by maven -->
+          <execution>
+            <id>default-testCompile</id>
+            <phase>none</phase>
+          </execution>
+          <execution>
+            <id>java-compile</id>
+            <phase>compile</phase>
+            <goals>
+              <goal>compile</goal>
+            </goals>
+          </execution>
+          <execution>
+            <id>java-test-compile</id>
+            <phase>test-compile</phase>
+            <goals>
+              <goal>testCompile</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <!-- Build fat jar -->
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-shade-plugin</artifactId>
+      </plugin>
+
+    </plugins>
+  </build>
+</project>

+ 15 - 0
frameworks/Kotlin/kooby/public/views/fortunes.rocker.html

@@ -0,0 +1,15 @@
+@import java.util.List
+@import kooby.Fortune
+@args (List<Fortune> fortunes)
+<!DOCTYPE html>
+<html>
+<head><title>Fortunes</title></head>
+<body>
+<table>
+    <tr><th>id</th><th>message</th></tr>
+    @for (it: fortunes) {
+    <tr><td>@it.getId()</td><td>@it.getMessage()</td></tr>
+    }
+</table>
+</body>
+</html>

+ 168 - 0
frameworks/Kotlin/kooby/src/main/kotlin/kooby/App.kt

@@ -0,0 +1,168 @@
+package kooby
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import io.jooby.BadRequestException
+import io.jooby.Context
+import io.jooby.ExecutionMode.EVENT_LOOP
+import io.jooby.MediaType.JSON
+import io.jooby.hikari.HikariModule
+import io.jooby.json.JacksonModule
+import io.jooby.require
+import io.jooby.rocker.Rockerby
+import io.jooby.runApp
+import java.util.*
+import java.util.concurrent.ThreadLocalRandom
+import javax.sql.DataSource
+
+private const val SELECT_WORLD = "select * from world where id=?"
+
+private const val DB_ROWS = 10000
+
+private const val MESSAGE = "Hello, World!"
+
+private val MESSAGE_BYTES = MESSAGE.toByteArray(Charsets.UTF_8)
+
+
+data class Message(val message: String = MESSAGE)
+
+class World(val id: Int, var randomNumber: Int)
+
+data class Fortune(val id: Int, var message: String)
+
+fun main(args: Array<String>) {
+  runApp(args, EVENT_LOOP) {
+
+    /** Server options (netty only): */
+    serverOptions {
+      singleLoop = true
+    }
+
+    /** JSON: */
+    install(JacksonModule())
+    val mapper = require(ObjectMapper::class)
+
+    /** Database: */
+    install(HikariModule())
+    val ds = require(DataSource::class)
+
+    /** Template engine: */
+    install(Rockerby())
+
+    get("/plaintext") {
+      ctx.send(MESSAGE_BYTES)
+    }
+
+    get("/json") {
+      ctx.setResponseType(JSON)
+      ctx.send(mapper.writeValueAsBytes(Message()))
+    }
+
+    /** Go blocking : */
+    dispatch {
+
+      /** Single query: */
+      get("/db") {
+        val rnd = ThreadLocalRandom.current()
+        val result = ds.connection.use { conn ->
+          conn.prepareStatement(SELECT_WORLD).use { statement ->
+            statement.setInt(1, nextRandom(rnd))
+            statement.executeQuery().use { rs ->
+              rs.next()
+              World(rs.getInt(1), rs.getInt(2))
+            }
+          }
+        }
+        ctx.setResponseType(JSON)
+        ctx.send(mapper.writeValueAsBytes(result))
+      }
+
+      /** Multiple query: */
+      get("/queries") {
+        val rnd = ThreadLocalRandom.current()
+        val queries = ctx.queries()
+        val result = ArrayList<World>(queries)
+        ds.connection.use { conn ->
+          conn.prepareStatement(SELECT_WORLD).use { statement ->
+            for (i in 1..queries) {
+              statement.setInt(1, nextRandom(rnd))
+              statement.executeQuery().use { rs ->
+                while (rs.next()) {
+                  result += World(rs.getInt(1), rs.getInt(2))
+                }
+              }
+            }
+          }
+        }
+        ctx.setResponseType(JSON)
+        ctx.send(mapper.writeValueAsBytes(result))
+      }
+
+      /** Updates: */
+      get("/updates") {
+        val queries = ctx.queries()
+        val result = ArrayList<World>(queries)
+        val rnd = ThreadLocalRandom.current()
+
+        val updateSql = StringJoiner(
+            ", ",
+            "UPDATE world SET randomNumber = temp.randomNumber FROM (VALUES ",
+            " ORDER BY 1) AS temp(id, randomNumber) WHERE temp.id = world.id")
+
+        ds.connection.use { connection ->
+          connection.prepareStatement(SELECT_WORLD).use { statement ->
+            for (i in 1..queries) {
+              statement.setInt(1, nextRandom(rnd))
+              statement.executeQuery().use { rs ->
+                rs.next()
+                result += World(rs.getInt("id"), rs.getInt("randomNumber"))
+              }
+              // prepare update query
+              updateSql.add("(?, ?)")
+            }
+          }
+
+          connection.prepareStatement(updateSql.toString()).use { statement ->
+            var i = 0
+            for (world in result) {
+              world.randomNumber = nextRandom(rnd)
+              statement.setInt(++i, world.id)
+              statement.setInt(++i, world.randomNumber)
+            }
+            statement.executeUpdate()
+          }
+        }
+        ctx.setResponseType(JSON)
+        ctx.send(mapper.writeValueAsBytes(result))
+      }
+
+      /** Fortunes: */
+      get("/fortunes") {
+        val fortunes = ArrayList<Fortune>()
+        ds.connection.use { connection ->
+          connection.prepareStatement("select * from fortune").use { stt ->
+            stt.executeQuery().use { rs ->
+              while (rs.next()) {
+                fortunes += Fortune(rs.getInt("id"), rs.getString("message"))
+              }
+            }
+          }
+        }
+        fortunes.add(Fortune(0, "Additional fortune added at request time."))
+        fortunes.sortBy { it.message }
+
+        /** render view:  */
+        views.fortunes.template(fortunes)
+      }
+    }
+  }
+}
+
+private fun nextRandom(rnd: Random): Int {
+  return rnd.nextInt(DB_ROWS) + 1
+}
+
+fun Context.queries() = try {
+  this.query("queries").intValue(1).coerceIn(1, 500)
+} catch (x: BadRequestException) {
+  1
+}