Skip to content

Commit

Permalink
fix: service instantiation uses proper type logic
Browse files Browse the repository at this point in the history
  • Loading branch information
LizAinslie committed Nov 14, 2024
1 parent d9d5585 commit 11ca26d
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 25 deletions.
30 changes: 16 additions & 14 deletions core/src/main/kotlin/sh/illumi/kraft/engine/ApplicationEngine.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package sh.illumi.kraft.engine

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import org.slf4j.Logger
import sh.illumi.kraft.KraftException
import sh.illumi.kraft.engine.ApplicationEngine.Default
import sh.illumi.kraft.layer.ApplicationLayer
import sh.illumi.kraft.util.argsMatchParams
import kotlin.reflect.KClass

/**
* An ApplicationEngine is the entry point for a Kraft application. It is
Expand All @@ -16,12 +18,18 @@ import sh.illumi.kraft.util.argsMatchParams
* created by extending this interface.
*/
interface ApplicationEngine {
val log: Logger get() = org.slf4j.LoggerFactory.getLogger(this.javaClass)
fun registerShutdownHook()

fun <TLayer : ApplicationLayer> createRoot(createRoot: () -> TLayer): TLayer {
val rootLayer = createRoot()
return rootLayer
}
fun <TRootLayer : ApplicationLayer> createRoot(
layerClass: KClass<TRootLayer>,
coroutineScope: CoroutineScope,
vararg constructorArgs: Any
): TRootLayer = layerClass.constructors.firstOrNull {
it.parameters.size == 1 + constructorArgs.size &&
it.parameters[0].type.classifier == CoroutineScope::class &&
argsMatchParams(constructorArgs, it.parameters.drop(1).toTypedArray())
}?.call(coroutineScope, *constructorArgs) ?: throw KraftException("Root layer has no suitable constructor")

interface Default : ApplicationEngine {
val coroutineScope get() = CoroutineScope(Dispatchers.Default)
Expand All @@ -36,18 +44,12 @@ interface ApplicationEngine {
}
}

inline fun <reified TLayer : ApplicationLayer> Default.startRoot(vararg constructorArgs: Any) {
rootLayer = createRoot<TLayer>(coroutineScope, *constructorArgs)
inline fun <reified TRootLayer : ApplicationLayer> Default.startRoot(vararg constructorArgs: Any) {
rootLayer = createRoot<TRootLayer>(coroutineScope, *constructorArgs)
rootLayer.start()
}

inline fun <reified TLayer : ApplicationLayer> ApplicationEngine.createRoot(
inline fun <reified TRootLayer : ApplicationLayer> ApplicationEngine.createRoot(
coroutineScope: CoroutineScope,
vararg constructorArgs: Any
) = createRoot {
TLayer::class.constructors.firstOrNull {
it.parameters.size == 1 + constructorArgs.size &&
it.parameters[0].type.classifier == CoroutineScope::class &&
argsMatchParams(constructorArgs, it.parameters.drop(1).toTypedArray())
}?.call(coroutineScope, *constructorArgs) ?: throw KraftException("Root layer has no suitable constructor")
}
) = createRoot(TRootLayer::class, coroutineScope, *constructorArgs)
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package sh.illumi.kraft.layer

import org.slf4j.LoggerFactory
import sh.illumi.kraft.KraftException

class LayerNavigationUtils(val applicationLayer: ApplicationLayer) {
private val log = LoggerFactory.getLogger(this.javaClass)

/**
* All the layers from this layer to the root layer
*/
Expand All @@ -16,6 +19,8 @@ class LayerNavigationUtils(val applicationLayer: ApplicationLayer) {
else break
}

log.debug("Layers from ${this.applicationLayer.javaClass.simpleName} to root: ${layers.joinToString { it.javaClass.simpleName }}")

return layers
}

Expand Down
26 changes: 16 additions & 10 deletions core/src/main/kotlin/sh/illumi/kraft/service/ServiceContainer.kt
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
package sh.illumi.kraft.service

import kotlinx.coroutines.CoroutineScope
import org.slf4j.LoggerFactory
import sh.illumi.kraft.KraftException
import sh.illumi.kraft.ServiceContainerException
import sh.illumi.kraft.layer.ApplicationLayer
import sh.illumi.kraft.util.argsMatchParams
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.KProperty
class ServiceContainer(
val applicationLayer: ApplicationLayer
) {
private val services = mutableMapOf<String, Service>()
private val log = LoggerFactory.getLogger(this.javaClass)

/**
* The expected length of the parameters for a service constructor in this layer
*/
val expectedParamsLength: Int
private val expectedParamsLength: Int
// add 2 because root layer has depth 0 and CoroutineScope is the first parameter
get() = applicationLayer.depth + 2

Expand All @@ -29,7 +32,10 @@ class ServiceContainer(
* @see getServiceConstructor
*/
inline fun <reified TService : Service> instantiateService(): TService =
getServiceConstructor<TService>()
instantiateService(TService::class)

fun <TService : Service> instantiateService(serviceClass: KClass<TService>): TService =
getServiceConstructor(serviceClass)
.call(
applicationLayer.coroutineScope,
*applicationLayer.layers.toRoot.reversed().toTypedArray()
Expand All @@ -42,15 +48,18 @@ class ServiceContainer(
* @throws KraftException If the service layer has no parent
*/
inline fun <reified TService : Service> getServiceConstructor(): KFunction<TService> =
TService::class.constructors.firstOrNull {
getServiceConstructor(TService::class)

fun <TService : Service> getServiceConstructor(serviceClass: KClass<TService>): KFunction<TService> =
serviceClass.constructors.firstOrNull {
if (it.parameters.size != expectedParamsLength) return@firstOrNull false
if (it.parameters[0].type.classifier != CoroutineScope::class) return@firstOrNull false

argsMatchParams(
applicationLayer.layers.toRoot.reversed().toTypedArray(),
it.parameters.drop(1).toTypedArray()
)
} ?: throw ServiceContainerException(this, "Service ${TService::class.simpleName} has no suitable constructor")

} ?: throw ServiceContainerException(this, "Service ${serviceClass.simpleName} has no suitable constructor")

/**
* Get a service by its [key][serviceKey]
Expand All @@ -66,12 +75,9 @@ class ServiceContainer(
* @return The service of type [TService]
* @throws KraftException If the service layer stack has no root or a parent is missing before the root layer
*/
inline fun <reified TService : Service> get(): TService {
val annotation = ServiceMetadata.resolveAnnotation(TService::class, this)

return get(annotation.key) as? TService
inline fun <reified TService : Service> get(): TService =
get(ServiceMetadata.resolveAnnotation(TService::class, this).key) as? TService
?: instantiateService<TService>().also { put(it) }
}

/**
* Register a [Service] in this layer. You can call this method with a
Expand Down
6 changes: 5 additions & 1 deletion core/src/main/kotlin/sh/illumi/kraft/util/reflect.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package sh.illumi.kraft.util

import kotlin.reflect.KParameter
import kotlin.reflect.full.createType
import kotlin.reflect.full.isSubtypeOf

fun argsMatchParams(args: Array<out Any>, params: Array<out KParameter>) =
if (args.size != params.size) false
else args.zip(params).all { (arg, param) -> param.type.classifier == arg::class }
else args.zip(params).all { (arg, param) ->
arg::class.createType().isSubtypeOf(param.type)
}

0 comments on commit 11ca26d

Please sign in to comment.