Navigation Menu

Skip to content

Commit

Permalink
Simple TODO application sample showing how to use kuick, ktor and kor…
Browse files Browse the repository at this point in the history
…te (#31)

* Simple TODO application sample showing how to use kuick, ktor and korte

* Try to use a cache that already exists for the todo list (though probably not used since getting all)

* More work

* Some fixes

* Small adjustment
  • Loading branch information
soywiz committed Apr 9, 2019
1 parent d7bd1c9 commit 8fb7042
Show file tree
Hide file tree
Showing 30 changed files with 527 additions and 67 deletions.
64 changes: 34 additions & 30 deletions build.gradle
Expand Up @@ -34,6 +34,8 @@ subprojects {
def isKuick = project.name.startsWith("kuick")
def multiProject = true
def includeJs = isKuick
def mustPublish = !project.path.startsWith(":samples")
//println(project.path)

if (multiProject) {
apply plugin: 'kotlin-multiplatform'
Expand Down Expand Up @@ -110,41 +112,43 @@ subprojects {
}
}

def publishUser = (rootProject.findProperty("BINTRAY_USER") ?: project.findProperty("bintrayUser") ?: System.getenv("BINTRAY_USER"))?.toString()
def publishPassword = (rootProject.findProperty("BINTRAY_KEY") ?: project.findProperty("bintrayApiKey") ?: System.getenv("BINTRAY_API_KEY"))?.toString()

if (publishUser != null && publishPassword != null) {
publishing {
repositories {
maven {
credentials {
username = publishUser
password = publishPassword
if (mustPublish) {
def publishUser = (rootProject.findProperty("BINTRAY_USER") ?: project.findProperty("bintrayUser") ?: System.getenv("BINTRAY_USER"))?.toString()
def publishPassword = (rootProject.findProperty("BINTRAY_KEY") ?: project.findProperty("bintrayApiKey") ?: System.getenv("BINTRAY_API_KEY"))?.toString()

if (publishUser != null && publishPassword != null) {
publishing {
repositories {
maven {
credentials {
username = publishUser
password = publishPassword
}
url = uri("https://api.bintray.com/maven/nospoonlab/nospoonlab/${project.property("project.repository")}/")
}
url = uri("https://api.bintray.com/maven/nospoonlab/nospoonlab/${project.property("project.repository")}/")
}
}
publications {
maven(MavenPublication) {
groupId = project.group
artifactId = project.name
version = project.version
publications {
maven(MavenPublication) {
groupId = project.group
artifactId = project.name
version = project.version

from multiProject ? components.kotlin : components.java
from multiProject ? components.kotlin : components.java

maven(MavenPublication) {
pom {
name = project.name
description = project.property("project.description")
url = project.property("project.scm.url")
licenses {
license {
name = project.property("project.license.name")
url = project.property("project.license.url")
}
}
scm {
maven(MavenPublication) {
pom {
name = project.name
description = project.property("project.description")
url = project.property("project.scm.url")
licenses {
license {
name = project.property("project.license.name")
url = project.property("project.license.url")
}
}
scm {
url = project.property("project.scm.url")
}
}
}
}
Expand Down
8 changes: 7 additions & 1 deletion kuick-core/src/commonMain/kotlin/kuick/models/id.kt
Expand Up @@ -6,4 +6,10 @@ interface Id { val id: String }
interface IdProvider {

fun randomId(): String
}
}

abstract class AbstractId(override val id: String) : kuick.models.Id {
override fun equals(other: Any?): Boolean = (other is AbstractId) && this.id == other.id
override fun hashCode(): Int = id.hashCode()
override fun toString(): String = id
}
@@ -1,6 +1,7 @@
package kuick.repositories.annotations

@Retention(AnnotationRetention.RUNTIME)
//@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.TYPE)
@Target(AnnotationTarget.FIELD)
annotation class MaxLength(val maxLength: Int)

Expand Down
@@ -1,26 +1,44 @@
package kuick.repositories.patterns

import kuick.repositories.*
import kuick.repositories.memory.ModelRepositoryMemory
import kotlin.reflect.KClass
import kotlin.reflect.KProperty1
import kuick.repositories.memory.*
import kotlin.reflect.*

interface Cache {
suspend fun <T:Any> get(key: String): T?
suspend fun <T:Any> put(key: String, cached: T)
suspend fun <T : Any> get(key: String): T?
suspend fun <T : Any> put(key: String, cached: T)
suspend fun remove(key: String)
}

class MemoryCache: Cache {

private val map: MutableMap<String, Any> = mutableMapOf()

override suspend fun <T : Any> get(key: String): T? {
val cached = map.get(key)
return cached?.let { it as T? }
}

override suspend fun <T : Any> put(key: String, cached: T) {
map.put(key, cached)
}

override suspend fun remove(key: String) {
map.remove(key)
}

}

/**
* [ModelRepository]
*/
class CachedModelRepository<I: Any, T: Any>(
class CachedModelRepository<I : Any, T : Any>(
val modelClass: KClass<T>,
override val idField: KProperty1<T, I>,
val repo: ModelRepository<I, T>,
private val cache: Cache,
private val cacheField: KProperty1<T, *>
): ModelRepositoryDecorator<I, T>(repo) {
) : ModelRepositoryDecorator<I, T>(repo) {

private suspend fun invalidate(t: T) = cache.remove(cacheField(t).toString())

Expand Down Expand Up @@ -49,7 +67,7 @@ class CachedModelRepository<I: Any, T: Any>(
subset = super.findBy(q)
cache.put(key, subset)
}
val subRepo = ModelRepositoryMemory<I,T>(modelClass, idField)
val subRepo = ModelRepositoryMemory<I, T>(modelClass, idField)
subset.forEach { subRepo.insert(it) }
subRepo.findBy(q)
} else {
Expand All @@ -58,9 +76,12 @@ class CachedModelRepository<I: Any, T: Any>(
}

private fun findCacheQuery(q: ModelQuery<T>): FieldEqs<T, *>? = when {
q is FieldEqs<T, *> && q.field == cacheField-> q
q is FieldEqs<T, *> && q.field == cacheField -> q
q is FilterExpAnd<T> -> findCacheQuery(q.left) ?: findCacheQuery(q.right)
else -> throw NotImplementedError("Missing hadling of query type: ${q}")
}

}
}

inline fun <I : Any, reified T : Any> ModelRepository<I, T>.cached(cache: Cache, cacheField: KProperty1<T, *> = idField) = CachedModelRepository(T::class, idField, this, cache, cacheField)

20 changes: 19 additions & 1 deletion kuick-core/src/jvmMain/kotlin/kuick/caching/GoogleMemoryCache.kt
@@ -1,12 +1,13 @@
package kuick.caching

import com.google.common.cache.CacheBuilder
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.*
import kuick.core.KuickInternal
import kuick.di.injector
import kuick.di.withInjectorContext
import java.time.Duration
import java.util.concurrent.TimeUnit
import kotlin.coroutines.*

class GoogleMemoryCache<K: Any, T:Any>(maxSize: Long, duration: Duration) : Cache<K, T> {

Expand All @@ -30,3 +31,20 @@ class GoogleMemoryCache<K: Any, T:Any>(maxSize: Long, duration: Duration) : Cach
override suspend fun invalidate(key: K) = cache.invalidate(key.toString())

}

class GoogleMemoryCache2<K: Any, T:Any>(maxSize: Long, duration: Duration) : Cache<K, T> {
private val cache = CacheBuilder.newBuilder()
.maximumSize(maxSize)
.expireAfterWrite(duration.seconds, TimeUnit.SECONDS)
.build<String, Deferred<T>>()

@UseExperimental(KuickInternal::class)
// @TODO: Shouldn't this be? Can't return T if we produce T?: suspend fun get(key: K, builder: suspend () -> T): T
override suspend fun get(key: K, builder: suspend () -> T): T {
val context = coroutineContext
val deferred = cache.get(key.toString()) { CoroutineScope(context).async(context) { builder() } }
return deferred.await()
}

override suspend fun invalidate(key: K) = cache.invalidate(key.toString())
}
2 changes: 1 addition & 1 deletion kuick-core/src/jvmMain/kotlin/kuick/di/InjectorExt.kt
Expand Up @@ -43,7 +43,7 @@ inline fun <reified T> Binder.bindSelf(): Binder = this.apply {
bind(T::class.java).asEagerSingleton()
}

class InjectorNotInContextException : RuntimeException()
class InjectorNotInContextException : RuntimeException("Injector not in context")

class InjectorContext(val injector: Injector) : AbstractCoroutineContextElement(Key) {
companion object Key : CoroutineContext.Key<InjectorContext>
Expand Down
9 changes: 9 additions & 0 deletions kuick-core/src/jvmMain/kotlin/kuick/di/PerCoroutineJob.kt
Expand Up @@ -6,6 +6,7 @@ import kuick.concurrent.atomic.*
import kuick.utils.*
import java.util.concurrent.atomic.*
import kotlin.concurrent.*
import kotlin.coroutines.*

@Singleton
class PerCoroutineJob @Inject constructor(val injector: Injector) {
Expand All @@ -16,6 +17,14 @@ class PerCoroutineJob @Inject constructor(val injector: Injector) {
return this
}

fun registerContext(context: CoroutineContext.Element) {
register { block ->
withContext(context) {
block()
}
}
}

fun runBlocking(callback: suspend () -> Unit) = kotlinx.coroutines.runBlocking { runSuspending(callback) }
suspend fun runSuspending(callback: suspend () -> Unit) = executeChunk(handlers, 0, callback)

Expand Down
5 changes: 5 additions & 0 deletions kuick-core/src/jvmMain/kotlin/kuick/utils/UUIDExt.kt
@@ -0,0 +1,5 @@
package kuick.utils

import java.util.*

fun randomUUID() = UUID.randomUUID().toString()
Expand Up @@ -11,27 +11,6 @@ import kotlin.test.assertNotEquals

class CachedModelRepositoryTest {


class MemoryCache: Cache {

private val map: MutableMap<String, Any> = mutableMapOf()

override suspend fun <T : Any> get(key: String): T? {
val cached = map.get(key)
return cached?.let { it as T? }
}

override suspend fun <T : Any> put(key: String, cached: T) {
map.put(key, cached)
}

override suspend fun remove(key: String) {
map.remove(key)
}

}


data class Question(val ebookId: String, val questionId: String, val question: String)

@Test
Expand Down
Expand Up @@ -12,7 +12,7 @@ object JdbcDriver : DbDriver {
//internal val Dispatchers = kotlinx.coroutines.Dispatchers.Default

override suspend fun connect(url: String): DbConnection = JdbcConnection(url, DriverManager.getConnection(url))
suspend fun connectMemoryH2() = connect("jdbc:h2:mem:0")
suspend fun connectMemoryH2(index: Int = 0) = connect("jdbc:h2:mem:$index")
}

class JdbcConnection(val url: String, val connection: Connection) : DbConnection {
Expand Down
Expand Up @@ -6,7 +6,9 @@ import kuick.orm.*
import kuick.repositories.*
import kotlin.reflect.*

class DbModelRepository<I : Any, T : Any>(
inline fun <reified T : Any, I : Any> DbModelRepository(idField: KProperty1<T, I>): ModelRepository<I, T> = DbModelRepository(T::class, idField)

open class DbModelRepository<I : Any, T : Any>(
val table: TableDefinition<T>,
override val idField: KProperty1<T, I>
) : ModelRepository<I, T> {
Expand Down
3 changes: 3 additions & 0 deletions kuick-http-ktor/build.gradle
@@ -1,5 +1,8 @@
dependencies {
jvmApi project(":kuick-http")
jvmMainApi "io.ktor:ktor-server-netty:$ktor_version"
jvmMainApi "io.ktor:ktor-locations:$ktor_version"
//jvmApi project(":kuick-db")
jvmTestImplementation "io.ktor:ktor-server-test-host:$ktor_version"
jvmTestImplementation project(":kuick-repositories-squash")
}
@@ -0,0 +1,38 @@
package kuick.ktor

/*
import io.ktor.application.*
import io.ktor.http.*
import io.ktor.request.*
import io.ktor.response.*
import io.ktor.routing.*
import kotlinx.coroutines.io.*
import kotlinx.io.core.*
import kuick.http.*
import kuick.http.HttpMethod
class KtorApplicationHttpServer(val app: Application) : HttpServer {
override suspend fun register(path: String, method: HttpMethod, handler: suspend (HttpRequest) -> HttpResponse) {
app.routing {
route(path, method.toKtor()) {
handle {
val response = handler(call.request.toKuick())
for ((key, value) in response.headers.headers) {
call.response.header(key, value)
}
call.respond(HttpStatusCode.fromValue(response.statusCode), response.retrieveBody())
}
}
}
}
fun HttpMethod.toKtor() = io.ktor.http.HttpMethod.parse(this.name)
fun io.ktor.http.HttpMethod.toKuick() = HttpMethod.parse(this.value)
fun io.ktor.http.Headers.toKuick() = kuick.http.HttpHeaders(this.entries().flatMap { pair -> pair.value.map { pair.key to it } })
suspend fun ApplicationRequest.toKuick(): HttpRequest {
return SimpleHttpRequest(this.path(), this.httpMethod.toKuick(), this.headers.toKuick(), this.receiveChannel().readRemaining().readBytes())
}
}
*/
@@ -0,0 +1,13 @@
package kuick.ktor

import com.google.inject.*
import io.ktor.application.*
import kuick.di.*
import kotlin.coroutines.*

fun Application.installContextPerRequest(injector: Injector, context: CoroutineContext.Element, callback: suspend () -> Unit) {
val perCoroutineJob = injector.get<PerCoroutineJob>()
perCoroutineJob.registerContext(context)
perCoroutineJob.runBlocking { callback() }
install(PerCoroutineJobFeature(injector))
}

0 comments on commit 8fb7042

Please sign in to comment.