From ba12415515db1b6d15dbd9557d80a81a4bcf7e7d Mon Sep 17 00:00:00 2001 From: Connor Wyatt Date: Wed, 1 Nov 2023 22:31:20 +0000 Subject: [PATCH 1/8] Add initial Server class and builder --- server/build.gradle.kts | 18 ++ .../io/connorwyatt/common/server/Server.kt | 175 ++++++++++++++++++ settings.gradle.kts | 12 ++ 3 files changed, 205 insertions(+) create mode 100644 server/build.gradle.kts create mode 100644 server/src/main/kotlin/io/connorwyatt/common/server/Server.kt diff --git a/server/build.gradle.kts b/server/build.gradle.kts new file mode 100644 index 0000000..168a310 --- /dev/null +++ b/server/build.gradle.kts @@ -0,0 +1,18 @@ +dependencies { + implementation(project(":configuration")) + implementation(project(":eventstore")) + implementation(project(":http")) + implementation(project(":mongodb")) + implementation(project(":rabbitmq")) + + implementation(libraries.kodein.di) + implementation(libraries.kodein.di.framework.ktor.server) + implementation(libraries.ktor.serialization.kotlinx.json) + implementation(libraries.ktor.server.callId) + implementation(libraries.ktor.server.callLogging) + implementation(libraries.ktor.server.contentNegotiation) + implementation(libraries.ktor.server.core) + implementation(libraries.ktor.server.netty) + implementation(libraries.ktor.server.requestValidation) + implementation(libraries.ktor.server.statusPages) +} diff --git a/server/src/main/kotlin/io/connorwyatt/common/server/Server.kt b/server/src/main/kotlin/io/connorwyatt/common/server/Server.kt new file mode 100644 index 0000000..8f344fb --- /dev/null +++ b/server/src/main/kotlin/io/connorwyatt/common/server/Server.kt @@ -0,0 +1,175 @@ +package io.connorwyatt.common.server + +import io.connorwyatt.common.eventstore.configuration.EventStoreConfiguration +import io.connorwyatt.common.eventstore.kodein.eventStoreDependenciesModule +import io.connorwyatt.common.eventstore.ktor.configureEventStore +import io.connorwyatt.common.http.validation.ValidationProblemResponse +import io.connorwyatt.common.mongodb.configuration.MongoDBConfiguration +import io.connorwyatt.common.mongodb.kodein.mongoDBDependenciesModule +import io.connorwyatt.common.mongodb.ktor.configureMongoDB +import io.connorwyatt.common.rabbitmq.configuration.RabbitMQConfiguration +import io.connorwyatt.common.rabbitmq.kodein.rabbitMQDependenciesModule +import io.connorwyatt.common.rabbitmq.ktor.configureRabbitMQ +import io.ktor.http.* +import io.ktor.serialization.kotlinx.json.* +import io.ktor.server.application.* +import io.ktor.server.engine.* +import io.ktor.server.netty.* +import io.ktor.server.plugins.callid.* +import io.ktor.server.plugins.callloging.* +import io.ktor.server.plugins.contentnegotiation.* +import io.ktor.server.plugins.requestvalidation.* +import io.ktor.server.plugins.statuspages.* +import io.ktor.server.request.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import java.util.* +import kotlinx.coroutines.runBlocking +import org.kodein.di.DI +import org.kodein.di.ktor.di + +class Server +internal constructor( + val port: Int, + private val diModules: List, + private val eventStoreConfiguration: EventStoreConfiguration?, + private val mongoDBConfiguration: MongoDBConfiguration?, + private val rabbitMQConfiguration: RabbitMQConfiguration?, + private val configureRequestValidation: (RequestValidationConfig.() -> Unit)?, + private val configureStatusPages: (StatusPagesConfig.() -> Unit)?, + private val configureRouting: (Routing.() -> Unit)?, +) { + fun start() { + embeddedServer(Netty, port = port, host = "localhost") { + runBlocking { + di { + eventStoreConfiguration?.let { import(eventStoreDependenciesModule(it)) } + mongoDBConfiguration?.let { import(mongoDBDependenciesModule(it)) } + rabbitMQConfiguration?.let { import(rabbitMQDependenciesModule(it)) } + importAll(diModules) + } + eventStoreConfiguration?.let { configureEventStore(it) } + mongoDBConfiguration?.let { configureMongoDB() } + rabbitMQConfiguration?.let { configureRabbitMQ(it) } + configureSerialization(this@embeddedServer) + configureRequestValidation(this@embeddedServer) + configureStatusPages(this@embeddedServer) + configureCallId(this@embeddedServer) + configureCallLogging(this@embeddedServer) + configureRouting(this@embeddedServer) + } + } + .start(wait = true) + } + + private fun configureSerialization(application: Application) { + application.install(ContentNegotiation) { json() } + } + + private fun configureRequestValidation(application: Application) { + configureRequestValidation?.let { + application.install(RequestValidation) { configureRequestValidation.invoke(this) } + } + } + + private fun configureStatusPages(application: Application) { + application.install(StatusPages) { + exception { call, cause -> + call.response.headers.append( + HttpHeaders.ContentType, + ContentType.Application.ProblemJson.toString() + ) + call.respond(HttpStatusCode.BadRequest, ValidationProblemResponse(cause.reasons)) + } + exception { call, _ -> + call.respondText("", ContentType.Any, status = HttpStatusCode.InternalServerError) + } + configureStatusPages?.invoke(this) + } + } + + private fun configureCallId(application: Application) { + application.install(CallId) { + generate { UUID.randomUUID().toString() } + replyToHeader(HttpHeaders.XRequestId) + } + } + + private fun configureCallLogging(application: Application) { + application.install(CallLogging) { + callIdMdc("request-id") + disableDefaultColors() + mdc("http-method") { call -> call.request.httpMethod.value } + mdc("request-url") { call -> call.request.uri } + mdc("status-code") { call -> call.response.status()?.value?.toString() } + } + } + + private fun configureRouting(application: Application) { + configureRouting?.let { application.routing { configureRouting.invoke(this) } } + } + + class Builder internal constructor() { + private var port: Int? = null + private var diModules = listOf() + private var eventStoreConfiguration: EventStoreConfiguration? = null + private var mongoDBConfiguration: MongoDBConfiguration? = null + private var rabbitMQConfiguration: RabbitMQConfiguration? = null + private var configureRequestValidation: (RequestValidationConfig.() -> Unit)? = null + private var configureStatusPages: (StatusPagesConfig.() -> Unit)? = null + private var configureRouting: (Routing.() -> Unit)? = null + + fun port(port: Int) { + this.port = port + } + + fun addDIModule(diModule: DI.Module) { + diModules = diModules.plus(diModule) + } + + fun addEventStore(eventStoreConfiguration: EventStoreConfiguration) { + this.eventStoreConfiguration = eventStoreConfiguration + } + + fun addMongoDB(mongoDBConfiguration: MongoDBConfiguration) { + this.mongoDBConfiguration = mongoDBConfiguration + } + + fun addRabbitMQ(rabbitMQConfiguration: RabbitMQConfiguration) { + this.rabbitMQConfiguration = rabbitMQConfiguration + } + + fun configureRequestValidation( + configureRequestValidation: RequestValidationConfig.() -> Unit + ) { + this.configureRequestValidation = configureRequestValidation + } + + fun configureStatusPages(configureStatusPages: StatusPagesConfig.() -> Unit) { + this.configureStatusPages = configureStatusPages + } + + fun configureRouting(configureRouting: Routing.() -> Unit) { + this.configureRouting = configureRouting + } + + fun build(): Server { + val port = port ?: throw Exception("Cannot build server without a port to listen on.") + + return Server( + port = port, + diModules = diModules, + eventStoreConfiguration = eventStoreConfiguration, + mongoDBConfiguration = mongoDBConfiguration, + rabbitMQConfiguration = rabbitMQConfiguration, + configureRequestValidation = configureRequestValidation, + configureStatusPages = configureStatusPages, + configureRouting = configureRouting, + ) + } + } + + companion object { + fun builder() = Builder() + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index a677ba6..62b9854 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -8,6 +8,7 @@ include(":http") include(":mongodb") include(":optional") include(":rabbitmq") +include(":server") include(":time") pluginManagement { @@ -66,7 +67,18 @@ dependencyResolutionManagement { ) library("ktor-client-cio", "io.ktor", "ktor-client-cio").version(ktorVersion) library("ktor-client-core", "io.ktor", "ktor-client-core").version(ktorVersion) + library("ktor-serialization-kotlinx-json", "io.ktor", "ktor-serialization-kotlinx-json").version( + ktorVersion + ) + library("ktor-server-callId","io.ktor", "ktor-server-call-id").version(ktorVersion) + library("ktor-server-callLogging","io.ktor", "ktor-server-call-logging").version(ktorVersion) + library("ktor-server-contentNegotiation", "io.ktor", "ktor-server-content-negotiation").version( + ktorVersion + ) library("ktor-server-core", "io.ktor", "ktor-server-core").version(ktorVersion) + library("ktor-server-netty", "io.ktor", "ktor-server-netty").version(ktorVersion) + library("ktor-server-requestValidation", "io.ktor","ktor-server-request-validation").version(ktorVersion) + library("ktor-server-statusPages", "io.ktor","ktor-server-status-pages").version(ktorVersion) library("mongoDB-driver", "org.mongodb", "mongodb-driver-kotlin-coroutine").version(mongoDBDriverVersion) library("rabbitMQ-client", "com.rabbitmq", "amqp-client").version( rabbitMQClientVersion From 3db27569082471469b184531fd2efc58b2d712e1 Mon Sep 17 00:00:00 2001 From: Connor Wyatt Date: Wed, 1 Nov 2023 22:35:23 +0000 Subject: [PATCH 2/8] Ensure mongodb configuration happens before eventstore configuration --- server/src/main/kotlin/io/connorwyatt/common/server/Server.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/kotlin/io/connorwyatt/common/server/Server.kt b/server/src/main/kotlin/io/connorwyatt/common/server/Server.kt index 8f344fb..52e0453 100644 --- a/server/src/main/kotlin/io/connorwyatt/common/server/Server.kt +++ b/server/src/main/kotlin/io/connorwyatt/common/server/Server.kt @@ -48,8 +48,8 @@ internal constructor( rabbitMQConfiguration?.let { import(rabbitMQDependenciesModule(it)) } importAll(diModules) } - eventStoreConfiguration?.let { configureEventStore(it) } mongoDBConfiguration?.let { configureMongoDB() } + eventStoreConfiguration?.let { configureEventStore(it) } rabbitMQConfiguration?.let { configureRabbitMQ(it) } configureSerialization(this@embeddedServer) configureRequestValidation(this@embeddedServer) From 5823c313033287c993647b4951d5893f44603cf8 Mon Sep 17 00:00:00 2001 From: Connor Wyatt Date: Wed, 1 Nov 2023 22:40:47 +0000 Subject: [PATCH 3/8] Ensure builder returns the builder for fluent API --- .../io/connorwyatt/common/server/Server.kt | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/server/src/main/kotlin/io/connorwyatt/common/server/Server.kt b/server/src/main/kotlin/io/connorwyatt/common/server/Server.kt index 52e0453..980265d 100644 --- a/server/src/main/kotlin/io/connorwyatt/common/server/Server.kt +++ b/server/src/main/kotlin/io/connorwyatt/common/server/Server.kt @@ -119,38 +119,46 @@ internal constructor( private var configureStatusPages: (StatusPagesConfig.() -> Unit)? = null private var configureRouting: (Routing.() -> Unit)? = null - fun port(port: Int) { + fun port(port: Int): Builder { this.port = port + return this } - fun addDIModule(diModule: DI.Module) { + fun addDIModule(diModule: DI.Module): Builder { diModules = diModules.plus(diModule) + return this } - fun addEventStore(eventStoreConfiguration: EventStoreConfiguration) { + fun addEventStore(eventStoreConfiguration: EventStoreConfiguration): Builder { this.eventStoreConfiguration = eventStoreConfiguration + return this } - fun addMongoDB(mongoDBConfiguration: MongoDBConfiguration) { + fun addMongoDB(mongoDBConfiguration: MongoDBConfiguration): Builder { this.mongoDBConfiguration = mongoDBConfiguration + return this } - fun addRabbitMQ(rabbitMQConfiguration: RabbitMQConfiguration) { + fun addRabbitMQ(rabbitMQConfiguration: RabbitMQConfiguration): Builder { this.rabbitMQConfiguration = rabbitMQConfiguration + return this } fun configureRequestValidation( configureRequestValidation: RequestValidationConfig.() -> Unit - ) { + ): Builder { this.configureRequestValidation = configureRequestValidation + return this } - fun configureStatusPages(configureStatusPages: StatusPagesConfig.() -> Unit) { + fun configureStatusPages(configureStatusPages: StatusPagesConfig.() -> Unit): Builder { this.configureStatusPages = configureStatusPages + return this } - fun configureRouting(configureRouting: Routing.() -> Unit) { + fun configureRouting(configureRouting: Routing.() -> Unit): Builder { this.configureRouting = configureRouting + return this } fun build(): Server { From efafcdf5134e4ca129a73d1d8ad4f2ffbca8636b Mon Sep 17 00:00:00 2001 From: Connor Wyatt Date: Wed, 1 Nov 2023 22:44:21 +0000 Subject: [PATCH 4/8] Change API to a builder API --- .../io/connorwyatt/common/server/Server.kt | 29 +++++++------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/server/src/main/kotlin/io/connorwyatt/common/server/Server.kt b/server/src/main/kotlin/io/connorwyatt/common/server/Server.kt index 980265d..549b5dc 100644 --- a/server/src/main/kotlin/io/connorwyatt/common/server/Server.kt +++ b/server/src/main/kotlin/io/connorwyatt/common/server/Server.kt @@ -119,49 +119,41 @@ internal constructor( private var configureStatusPages: (StatusPagesConfig.() -> Unit)? = null private var configureRouting: (Routing.() -> Unit)? = null - fun port(port: Int): Builder { + fun port(port: Int) { this.port = port - return this } - fun addDIModule(diModule: DI.Module): Builder { + fun addDIModule(diModule: DI.Module) { diModules = diModules.plus(diModule) - return this } - fun addEventStore(eventStoreConfiguration: EventStoreConfiguration): Builder { + fun addEventStore(eventStoreConfiguration: EventStoreConfiguration) { this.eventStoreConfiguration = eventStoreConfiguration - return this } - fun addMongoDB(mongoDBConfiguration: MongoDBConfiguration): Builder { + fun addMongoDB(mongoDBConfiguration: MongoDBConfiguration) { this.mongoDBConfiguration = mongoDBConfiguration - return this } - fun addRabbitMQ(rabbitMQConfiguration: RabbitMQConfiguration): Builder { + fun addRabbitMQ(rabbitMQConfiguration: RabbitMQConfiguration) { this.rabbitMQConfiguration = rabbitMQConfiguration - return this } fun configureRequestValidation( configureRequestValidation: RequestValidationConfig.() -> Unit - ): Builder { + ) { this.configureRequestValidation = configureRequestValidation - return this } - fun configureStatusPages(configureStatusPages: StatusPagesConfig.() -> Unit): Builder { + fun configureStatusPages(configureStatusPages: StatusPagesConfig.() -> Unit) { this.configureStatusPages = configureStatusPages - return this } - fun configureRouting(configureRouting: Routing.() -> Unit): Builder { + fun configureRouting(configureRouting: Routing.() -> Unit) { this.configureRouting = configureRouting - return this } - fun build(): Server { + internal fun build(): Server { val port = port ?: throw Exception("Cannot build server without a port to listen on.") return Server( @@ -178,6 +170,7 @@ internal constructor( } companion object { - fun builder() = Builder() + fun build(builder: Builder.() -> Unit): Server = + Builder().apply { builder.invoke(this) }.build() } } From 9b7eb67e03f25c2d21f396c677b0786f5b62adff Mon Sep 17 00:00:00 2001 From: Connor Wyatt Date: Sat, 4 Nov 2023 15:21:06 +0000 Subject: [PATCH 5/8] Add http and time to builder and refactor builder --- server/build.gradle.kts | 1 + .../io/connorwyatt/common/server/Server.kt | 181 +++++++++--------- 2 files changed, 96 insertions(+), 86 deletions(-) diff --git a/server/build.gradle.kts b/server/build.gradle.kts index 168a310..ce2447c 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -4,6 +4,7 @@ dependencies { implementation(project(":http")) implementation(project(":mongodb")) implementation(project(":rabbitmq")) + implementation(project(":time")) implementation(libraries.kodein.di) implementation(libraries.kodein.di.framework.ktor.server) diff --git a/server/src/main/kotlin/io/connorwyatt/common/server/Server.kt b/server/src/main/kotlin/io/connorwyatt/common/server/Server.kt index 549b5dc..453268d 100644 --- a/server/src/main/kotlin/io/connorwyatt/common/server/Server.kt +++ b/server/src/main/kotlin/io/connorwyatt/common/server/Server.kt @@ -3,6 +3,7 @@ package io.connorwyatt.common.server import io.connorwyatt.common.eventstore.configuration.EventStoreConfiguration import io.connorwyatt.common.eventstore.kodein.eventStoreDependenciesModule import io.connorwyatt.common.eventstore.ktor.configureEventStore +import io.connorwyatt.common.http.httpDependenciesModule import io.connorwyatt.common.http.validation.ValidationProblemResponse import io.connorwyatt.common.mongodb.configuration.MongoDBConfiguration import io.connorwyatt.common.mongodb.kodein.mongoDBDependenciesModule @@ -10,6 +11,7 @@ import io.connorwyatt.common.mongodb.ktor.configureMongoDB import io.connorwyatt.common.rabbitmq.configuration.RabbitMQConfiguration import io.connorwyatt.common.rabbitmq.kodein.rabbitMQDependenciesModule import io.connorwyatt.common.rabbitmq.ktor.configureRabbitMQ +import io.connorwyatt.common.time.timeDependenciesModule import io.ktor.http.* import io.ktor.serialization.kotlinx.json.* import io.ktor.server.application.* @@ -28,85 +30,9 @@ import kotlinx.coroutines.runBlocking import org.kodein.di.DI import org.kodein.di.ktor.di -class Server -internal constructor( - val port: Int, - private val diModules: List, - private val eventStoreConfiguration: EventStoreConfiguration?, - private val mongoDBConfiguration: MongoDBConfiguration?, - private val rabbitMQConfiguration: RabbitMQConfiguration?, - private val configureRequestValidation: (RequestValidationConfig.() -> Unit)?, - private val configureStatusPages: (StatusPagesConfig.() -> Unit)?, - private val configureRouting: (Routing.() -> Unit)?, -) { +class Server internal constructor(private val embeddedServer: ApplicationEngine) { fun start() { - embeddedServer(Netty, port = port, host = "localhost") { - runBlocking { - di { - eventStoreConfiguration?.let { import(eventStoreDependenciesModule(it)) } - mongoDBConfiguration?.let { import(mongoDBDependenciesModule(it)) } - rabbitMQConfiguration?.let { import(rabbitMQDependenciesModule(it)) } - importAll(diModules) - } - mongoDBConfiguration?.let { configureMongoDB() } - eventStoreConfiguration?.let { configureEventStore(it) } - rabbitMQConfiguration?.let { configureRabbitMQ(it) } - configureSerialization(this@embeddedServer) - configureRequestValidation(this@embeddedServer) - configureStatusPages(this@embeddedServer) - configureCallId(this@embeddedServer) - configureCallLogging(this@embeddedServer) - configureRouting(this@embeddedServer) - } - } - .start(wait = true) - } - - private fun configureSerialization(application: Application) { - application.install(ContentNegotiation) { json() } - } - - private fun configureRequestValidation(application: Application) { - configureRequestValidation?.let { - application.install(RequestValidation) { configureRequestValidation.invoke(this) } - } - } - - private fun configureStatusPages(application: Application) { - application.install(StatusPages) { - exception { call, cause -> - call.response.headers.append( - HttpHeaders.ContentType, - ContentType.Application.ProblemJson.toString() - ) - call.respond(HttpStatusCode.BadRequest, ValidationProblemResponse(cause.reasons)) - } - exception { call, _ -> - call.respondText("", ContentType.Any, status = HttpStatusCode.InternalServerError) - } - configureStatusPages?.invoke(this) - } - } - - private fun configureCallId(application: Application) { - application.install(CallId) { - generate { UUID.randomUUID().toString() } - replyToHeader(HttpHeaders.XRequestId) - } - } - - private fun configureCallLogging(application: Application) { - application.install(CallLogging) { - callIdMdc("request-id") - disableDefaultColors() - mdc("http-method") { call -> call.request.httpMethod.value } - mdc("request-url") { call -> call.request.uri } - mdc("status-code") { call -> call.response.status()?.value?.toString() } - } - } - - private fun configureRouting(application: Application) { - configureRouting?.let { application.routing { configureRouting.invoke(this) } } + embeddedServer.start(wait = true) } class Builder internal constructor() { @@ -115,6 +41,8 @@ internal constructor( private var eventStoreConfiguration: EventStoreConfiguration? = null private var mongoDBConfiguration: MongoDBConfiguration? = null private var rabbitMQConfiguration: RabbitMQConfiguration? = null + private var http: Boolean = false + private var time: Boolean = false private var configureRequestValidation: (RequestValidationConfig.() -> Unit)? = null private var configureStatusPages: (StatusPagesConfig.() -> Unit)? = null private var configureRouting: (Routing.() -> Unit)? = null @@ -139,6 +67,14 @@ internal constructor( this.rabbitMQConfiguration = rabbitMQConfiguration } + fun addHttp() { + this.http = true + } + + fun addTime() { + this.time = true + } + fun configureRequestValidation( configureRequestValidation: RequestValidationConfig.() -> Unit ) { @@ -157,16 +93,89 @@ internal constructor( val port = port ?: throw Exception("Cannot build server without a port to listen on.") return Server( - port = port, - diModules = diModules, - eventStoreConfiguration = eventStoreConfiguration, - mongoDBConfiguration = mongoDBConfiguration, - rabbitMQConfiguration = rabbitMQConfiguration, - configureRequestValidation = configureRequestValidation, - configureStatusPages = configureStatusPages, - configureRouting = configureRouting, + embeddedServer(Netty, port = port, host = "localhost") { + runBlocking { + di { + eventStoreConfiguration?.let { + import(eventStoreDependenciesModule(it)) + } + mongoDBConfiguration?.let { import(mongoDBDependenciesModule(it)) } + rabbitMQConfiguration?.let { import(rabbitMQDependenciesModule(it)) } + if (http) { + import(httpDependenciesModule) + } + if (time) { + import(timeDependenciesModule) + } + importAll(diModules) + } + mongoDBConfiguration?.let { configureMongoDB() } + eventStoreConfiguration?.let { configureEventStore(it) } + rabbitMQConfiguration?.let { configureRabbitMQ(it) } + configureSerialization(this@embeddedServer) + configureRequestValidation(this@embeddedServer) + configureStatusPages(this@embeddedServer) + configureCallId(this@embeddedServer) + configureCallLogging(this@embeddedServer) + configureRouting(this@embeddedServer) + } + } ) } + + private fun configureSerialization(application: Application) { + application.install(ContentNegotiation) { json() } + } + + private fun configureRequestValidation(application: Application) { + configureRequestValidation?.let { configure -> + application.install(RequestValidation) { configure.invoke(this) } + } + } + + private fun configureStatusPages(application: Application) { + application.install(StatusPages) { + exception { call, cause -> + call.response.headers.append( + HttpHeaders.ContentType, + ContentType.Application.ProblemJson.toString() + ) + call.respond( + HttpStatusCode.BadRequest, + ValidationProblemResponse(cause.reasons) + ) + } + exception { call, _ -> + call.respondText( + "", + ContentType.Any, + status = HttpStatusCode.InternalServerError + ) + } + configureStatusPages?.invoke(this) + } + } + + private fun configureCallId(application: Application) { + application.install(CallId) { + generate { UUID.randomUUID().toString() } + replyToHeader(HttpHeaders.XRequestId) + } + } + + private fun configureCallLogging(application: Application) { + application.install(CallLogging) { + callIdMdc("request-id") + disableDefaultColors() + mdc("http-method") { call -> call.request.httpMethod.value } + mdc("request-url") { call -> call.request.uri } + mdc("status-code") { call -> call.response.status()?.value?.toString() } + } + } + + private fun configureRouting(application: Application) { + configureRouting?.let { configure -> application.routing { configure.invoke(this) } } + } } companion object { From 82144cf6439ce11b6ba2a28e76fe8e367c933748 Mon Sep 17 00:00:00 2001 From: Connor Wyatt Date: Sat, 4 Nov 2023 18:00:13 +0000 Subject: [PATCH 6/8] Use CIO as engine --- server/build.gradle.kts | 2 +- server/src/main/kotlin/io/connorwyatt/common/server/Server.kt | 4 ++-- settings.gradle.kts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/server/build.gradle.kts b/server/build.gradle.kts index ce2447c..87c5a66 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -11,9 +11,9 @@ dependencies { implementation(libraries.ktor.serialization.kotlinx.json) implementation(libraries.ktor.server.callId) implementation(libraries.ktor.server.callLogging) + implementation(libraries.ktor.server.cio) implementation(libraries.ktor.server.contentNegotiation) implementation(libraries.ktor.server.core) - implementation(libraries.ktor.server.netty) implementation(libraries.ktor.server.requestValidation) implementation(libraries.ktor.server.statusPages) } diff --git a/server/src/main/kotlin/io/connorwyatt/common/server/Server.kt b/server/src/main/kotlin/io/connorwyatt/common/server/Server.kt index 453268d..c6c13ba 100644 --- a/server/src/main/kotlin/io/connorwyatt/common/server/Server.kt +++ b/server/src/main/kotlin/io/connorwyatt/common/server/Server.kt @@ -15,8 +15,8 @@ import io.connorwyatt.common.time.timeDependenciesModule import io.ktor.http.* import io.ktor.serialization.kotlinx.json.* import io.ktor.server.application.* +import io.ktor.server.cio.* import io.ktor.server.engine.* -import io.ktor.server.netty.* import io.ktor.server.plugins.callid.* import io.ktor.server.plugins.callloging.* import io.ktor.server.plugins.contentnegotiation.* @@ -93,7 +93,7 @@ class Server internal constructor(private val embeddedServer: ApplicationEngine) val port = port ?: throw Exception("Cannot build server without a port to listen on.") return Server( - embeddedServer(Netty, port = port, host = "localhost") { + embeddedServer(CIO, port = port, host = "localhost") { runBlocking { di { eventStoreConfiguration?.let { diff --git a/settings.gradle.kts b/settings.gradle.kts index 62b9854..689f864 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -72,11 +72,11 @@ dependencyResolutionManagement { ) library("ktor-server-callId","io.ktor", "ktor-server-call-id").version(ktorVersion) library("ktor-server-callLogging","io.ktor", "ktor-server-call-logging").version(ktorVersion) + library("ktor-server-cio", "io.ktor", "ktor-server-cio").version(ktorVersion) library("ktor-server-contentNegotiation", "io.ktor", "ktor-server-content-negotiation").version( ktorVersion ) library("ktor-server-core", "io.ktor", "ktor-server-core").version(ktorVersion) - library("ktor-server-netty", "io.ktor", "ktor-server-netty").version(ktorVersion) library("ktor-server-requestValidation", "io.ktor","ktor-server-request-validation").version(ktorVersion) library("ktor-server-statusPages", "io.ktor","ktor-server-status-pages").version(ktorVersion) library("mongoDB-driver", "org.mongodb", "mongodb-driver-kotlin-coroutine").version(mongoDBDriverVersion) From 29d2b22abfa1b7d7879c03bfd2bebac8320986bb Mon Sep 17 00:00:00 2001 From: Connor Wyatt Date: Sat, 4 Nov 2023 22:09:08 +0000 Subject: [PATCH 7/8] Refactor to make the solution work with the test application --- .../common/server/ApplicationConfiguration.kt | 94 +++++++++ .../common/server/ApplicationExt.kt | 60 ++++++ .../io/connorwyatt/common/server/Server.kt | 182 +----------------- 3 files changed, 160 insertions(+), 176 deletions(-) create mode 100644 server/src/main/kotlin/io/connorwyatt/common/server/ApplicationConfiguration.kt create mode 100644 server/src/main/kotlin/io/connorwyatt/common/server/ApplicationExt.kt diff --git a/server/src/main/kotlin/io/connorwyatt/common/server/ApplicationConfiguration.kt b/server/src/main/kotlin/io/connorwyatt/common/server/ApplicationConfiguration.kt new file mode 100644 index 0000000..8e63279 --- /dev/null +++ b/server/src/main/kotlin/io/connorwyatt/common/server/ApplicationConfiguration.kt @@ -0,0 +1,94 @@ +package io.connorwyatt.common.server + +import io.connorwyatt.common.eventstore.configuration.EventStoreConfiguration +import io.connorwyatt.common.eventstore.kodein.eventStoreDependenciesModule +import io.connorwyatt.common.eventstore.ktor.configureEventStore +import io.connorwyatt.common.http.httpDependenciesModule +import io.connorwyatt.common.mongodb.configuration.MongoDBConfiguration +import io.connorwyatt.common.mongodb.kodein.mongoDBDependenciesModule +import io.connorwyatt.common.mongodb.ktor.configureMongoDB +import io.connorwyatt.common.rabbitmq.configuration.RabbitMQConfiguration +import io.connorwyatt.common.rabbitmq.kodein.rabbitMQDependenciesModule +import io.connorwyatt.common.rabbitmq.ktor.configureRabbitMQ +import io.connorwyatt.common.time.timeDependenciesModule +import io.ktor.server.application.* +import io.ktor.server.plugins.requestvalidation.* +import io.ktor.server.plugins.statuspages.* +import io.ktor.server.routing.* +import kotlinx.coroutines.runBlocking +import org.kodein.di.DI +import org.kodein.di.ktor.di + +class ApplicationConfiguration { + private var diModules = listOf() + private var eventStoreConfiguration: EventStoreConfiguration? = null + private var mongoDBConfiguration: MongoDBConfiguration? = null + private var rabbitMQConfiguration: RabbitMQConfiguration? = null + private var http: Boolean = false + private var time: Boolean = false + private var configureRequestValidation: (RequestValidationConfig.() -> Unit)? = null + private var configureStatusPages: (StatusPagesConfig.() -> Unit)? = null + private var configureRouting: (Routing.() -> Unit)? = null + + fun addDIModule(diModule: DI.Module) { + diModules = diModules.plus(diModule) + } + + fun addEventStore(eventStoreConfiguration: EventStoreConfiguration) { + this.eventStoreConfiguration = eventStoreConfiguration + } + + fun addMongoDB(mongoDBConfiguration: MongoDBConfiguration) { + this.mongoDBConfiguration = mongoDBConfiguration + } + + fun addRabbitMQ(rabbitMQConfiguration: RabbitMQConfiguration) { + this.rabbitMQConfiguration = rabbitMQConfiguration + } + + fun addHttp() { + this.http = true + } + + fun addTime() { + this.time = true + } + + fun configureRequestValidation(configureRequestValidation: RequestValidationConfig.() -> Unit) { + this.configureRequestValidation = configureRequestValidation + } + + fun configureStatusPages(configureStatusPages: StatusPagesConfig.() -> Unit) { + this.configureStatusPages = configureStatusPages + } + + fun configureRouting(configureRouting: Routing.() -> Unit) { + this.configureRouting = configureRouting + } + + fun applyTo(application: Application) { + application.apply { + di { + eventStoreConfiguration?.let { import(eventStoreDependenciesModule(it)) } + mongoDBConfiguration?.let { import(mongoDBDependenciesModule(it)) } + rabbitMQConfiguration?.let { import(rabbitMQDependenciesModule(it)) } + if (http) { + import(httpDependenciesModule) + } + if (time) { + import(timeDependenciesModule) + } + importAll(diModules) + } + mongoDBConfiguration?.let { runBlocking { configureMongoDB() } } + eventStoreConfiguration?.let { configureEventStore(it) } + rabbitMQConfiguration?.let { configureRabbitMQ(it) } + configureSerialization() + configureRequestValidation?.let { configureRequestValidation(it) } + configureStatusPages(configureStatusPages) + configureCallId() + configureCallLogging() + configureRouting?.let { configureRouting(it) } + } + } +} diff --git a/server/src/main/kotlin/io/connorwyatt/common/server/ApplicationExt.kt b/server/src/main/kotlin/io/connorwyatt/common/server/ApplicationExt.kt new file mode 100644 index 0000000..a04d863 --- /dev/null +++ b/server/src/main/kotlin/io/connorwyatt/common/server/ApplicationExt.kt @@ -0,0 +1,60 @@ +package io.connorwyatt.common.server + +import io.connorwyatt.common.http.validation.ValidationProblemResponse +import io.ktor.http.* +import io.ktor.serialization.kotlinx.json.* +import io.ktor.server.application.* +import io.ktor.server.plugins.callid.* +import io.ktor.server.plugins.callloging.* +import io.ktor.server.plugins.contentnegotiation.* +import io.ktor.server.plugins.requestvalidation.* +import io.ktor.server.plugins.statuspages.* +import io.ktor.server.request.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import java.util.* + +internal fun Application.configureSerialization() { + install(ContentNegotiation) { json() } +} + +internal fun Application.configureRequestValidation(configure: RequestValidationConfig.() -> Unit) { + install(RequestValidation) { configure.invoke(this) } +} + +internal fun Application.configureStatusPages(configure: (StatusPagesConfig.() -> Unit)? = null) { + install(StatusPages) { + exception { call, cause -> + call.response.headers.append( + HttpHeaders.ContentType, + ContentType.Application.ProblemJson.toString() + ) + call.respond(HttpStatusCode.BadRequest, ValidationProblemResponse(cause.reasons)) + } + exception { call, _ -> + call.respondText("", ContentType.Any, status = HttpStatusCode.InternalServerError) + } + configure?.invoke(this) + } +} + +internal fun Application.configureCallId() { + install(CallId) { + generate { UUID.randomUUID().toString() } + replyToHeader(HttpHeaders.XRequestId) + } +} + +internal fun Application.configureCallLogging() { + install(CallLogging) { + callIdMdc("request-id") + disableDefaultColors() + mdc("http-method") { call -> call.request.httpMethod.value } + mdc("request-url") { call -> call.request.uri } + mdc("status-code") { call -> call.response.status()?.value?.toString() } + } +} + +internal fun Application.configureRouting(configure: Routing.() -> Unit) { + routing { configure.invoke(this) } +} diff --git a/server/src/main/kotlin/io/connorwyatt/common/server/Server.kt b/server/src/main/kotlin/io/connorwyatt/common/server/Server.kt index c6c13ba..3c32b36 100644 --- a/server/src/main/kotlin/io/connorwyatt/common/server/Server.kt +++ b/server/src/main/kotlin/io/connorwyatt/common/server/Server.kt @@ -1,185 +1,15 @@ package io.connorwyatt.common.server -import io.connorwyatt.common.eventstore.configuration.EventStoreConfiguration -import io.connorwyatt.common.eventstore.kodein.eventStoreDependenciesModule -import io.connorwyatt.common.eventstore.ktor.configureEventStore -import io.connorwyatt.common.http.httpDependenciesModule -import io.connorwyatt.common.http.validation.ValidationProblemResponse -import io.connorwyatt.common.mongodb.configuration.MongoDBConfiguration -import io.connorwyatt.common.mongodb.kodein.mongoDBDependenciesModule -import io.connorwyatt.common.mongodb.ktor.configureMongoDB -import io.connorwyatt.common.rabbitmq.configuration.RabbitMQConfiguration -import io.connorwyatt.common.rabbitmq.kodein.rabbitMQDependenciesModule -import io.connorwyatt.common.rabbitmq.ktor.configureRabbitMQ -import io.connorwyatt.common.time.timeDependenciesModule -import io.ktor.http.* -import io.ktor.serialization.kotlinx.json.* -import io.ktor.server.application.* import io.ktor.server.cio.* import io.ktor.server.engine.* -import io.ktor.server.plugins.callid.* -import io.ktor.server.plugins.callloging.* -import io.ktor.server.plugins.contentnegotiation.* -import io.ktor.server.plugins.requestvalidation.* -import io.ktor.server.plugins.statuspages.* -import io.ktor.server.request.* -import io.ktor.server.response.* -import io.ktor.server.routing.* -import java.util.* -import kotlinx.coroutines.runBlocking -import org.kodein.di.DI -import org.kodein.di.ktor.di -class Server internal constructor(private val embeddedServer: ApplicationEngine) { - fun start() { - embeddedServer.start(wait = true) - } - - class Builder internal constructor() { - private var port: Int? = null - private var diModules = listOf() - private var eventStoreConfiguration: EventStoreConfiguration? = null - private var mongoDBConfiguration: MongoDBConfiguration? = null - private var rabbitMQConfiguration: RabbitMQConfiguration? = null - private var http: Boolean = false - private var time: Boolean = false - private var configureRequestValidation: (RequestValidationConfig.() -> Unit)? = null - private var configureStatusPages: (StatusPagesConfig.() -> Unit)? = null - private var configureRouting: (Routing.() -> Unit)? = null - - fun port(port: Int) { - this.port = port - } - - fun addDIModule(diModule: DI.Module) { - diModules = diModules.plus(diModule) - } - - fun addEventStore(eventStoreConfiguration: EventStoreConfiguration) { - this.eventStoreConfiguration = eventStoreConfiguration - } - - fun addMongoDB(mongoDBConfiguration: MongoDBConfiguration) { - this.mongoDBConfiguration = mongoDBConfiguration - } - - fun addRabbitMQ(rabbitMQConfiguration: RabbitMQConfiguration) { - this.rabbitMQConfiguration = rabbitMQConfiguration - } - - fun addHttp() { - this.http = true - } - - fun addTime() { - this.time = true - } - - fun configureRequestValidation( - configureRequestValidation: RequestValidationConfig.() -> Unit - ) { - this.configureRequestValidation = configureRequestValidation - } - - fun configureStatusPages(configureStatusPages: StatusPagesConfig.() -> Unit) { - this.configureStatusPages = configureStatusPages +class Server(port: Int, private val applicationConfiguration: ApplicationConfiguration) { + private val applicationEngine = + embeddedServer(CIO, port = port, host = "localhost") { + applicationConfiguration.applyTo(this) } - fun configureRouting(configureRouting: Routing.() -> Unit) { - this.configureRouting = configureRouting - } - - internal fun build(): Server { - val port = port ?: throw Exception("Cannot build server without a port to listen on.") - - return Server( - embeddedServer(CIO, port = port, host = "localhost") { - runBlocking { - di { - eventStoreConfiguration?.let { - import(eventStoreDependenciesModule(it)) - } - mongoDBConfiguration?.let { import(mongoDBDependenciesModule(it)) } - rabbitMQConfiguration?.let { import(rabbitMQDependenciesModule(it)) } - if (http) { - import(httpDependenciesModule) - } - if (time) { - import(timeDependenciesModule) - } - importAll(diModules) - } - mongoDBConfiguration?.let { configureMongoDB() } - eventStoreConfiguration?.let { configureEventStore(it) } - rabbitMQConfiguration?.let { configureRabbitMQ(it) } - configureSerialization(this@embeddedServer) - configureRequestValidation(this@embeddedServer) - configureStatusPages(this@embeddedServer) - configureCallId(this@embeddedServer) - configureCallLogging(this@embeddedServer) - configureRouting(this@embeddedServer) - } - } - ) - } - - private fun configureSerialization(application: Application) { - application.install(ContentNegotiation) { json() } - } - - private fun configureRequestValidation(application: Application) { - configureRequestValidation?.let { configure -> - application.install(RequestValidation) { configure.invoke(this) } - } - } - - private fun configureStatusPages(application: Application) { - application.install(StatusPages) { - exception { call, cause -> - call.response.headers.append( - HttpHeaders.ContentType, - ContentType.Application.ProblemJson.toString() - ) - call.respond( - HttpStatusCode.BadRequest, - ValidationProblemResponse(cause.reasons) - ) - } - exception { call, _ -> - call.respondText( - "", - ContentType.Any, - status = HttpStatusCode.InternalServerError - ) - } - configureStatusPages?.invoke(this) - } - } - - private fun configureCallId(application: Application) { - application.install(CallId) { - generate { UUID.randomUUID().toString() } - replyToHeader(HttpHeaders.XRequestId) - } - } - - private fun configureCallLogging(application: Application) { - application.install(CallLogging) { - callIdMdc("request-id") - disableDefaultColors() - mdc("http-method") { call -> call.request.httpMethod.value } - mdc("request-url") { call -> call.request.uri } - mdc("status-code") { call -> call.response.status()?.value?.toString() } - } - } - - private fun configureRouting(application: Application) { - configureRouting?.let { configure -> application.routing { configure.invoke(this) } } - } - } - - companion object { - fun build(builder: Builder.() -> Unit): Server = - Builder().apply { builder.invoke(this) }.build() + fun start() { + applicationEngine.start(wait = true) } } From d082eebbc88e4d96176c9e35124378f01b210c15 Mon Sep 17 00:00:00 2001 From: Connor Wyatt Date: Sat, 4 Nov 2023 22:44:50 +0000 Subject: [PATCH 8/8] Ensure that DI is exposed --- .../common/server/ApplicationConfiguration.kt | 139 ++++++++++-------- 1 file changed, 74 insertions(+), 65 deletions(-) diff --git a/server/src/main/kotlin/io/connorwyatt/common/server/ApplicationConfiguration.kt b/server/src/main/kotlin/io/connorwyatt/common/server/ApplicationConfiguration.kt index 8e63279..b3c9fde 100644 --- a/server/src/main/kotlin/io/connorwyatt/common/server/ApplicationConfiguration.kt +++ b/server/src/main/kotlin/io/connorwyatt/common/server/ApplicationConfiguration.kt @@ -1,16 +1,11 @@ package io.connorwyatt.common.server import io.connorwyatt.common.eventstore.configuration.EventStoreConfiguration -import io.connorwyatt.common.eventstore.kodein.eventStoreDependenciesModule import io.connorwyatt.common.eventstore.ktor.configureEventStore -import io.connorwyatt.common.http.httpDependenciesModule import io.connorwyatt.common.mongodb.configuration.MongoDBConfiguration -import io.connorwyatt.common.mongodb.kodein.mongoDBDependenciesModule import io.connorwyatt.common.mongodb.ktor.configureMongoDB import io.connorwyatt.common.rabbitmq.configuration.RabbitMQConfiguration -import io.connorwyatt.common.rabbitmq.kodein.rabbitMQDependenciesModule import io.connorwyatt.common.rabbitmq.ktor.configureRabbitMQ -import io.connorwyatt.common.time.timeDependenciesModule import io.ktor.server.application.* import io.ktor.server.plugins.requestvalidation.* import io.ktor.server.plugins.statuspages.* @@ -19,76 +14,90 @@ import kotlinx.coroutines.runBlocking import org.kodein.di.DI import org.kodein.di.ktor.di -class ApplicationConfiguration { - private var diModules = listOf() - private var eventStoreConfiguration: EventStoreConfiguration? = null - private var mongoDBConfiguration: MongoDBConfiguration? = null - private var rabbitMQConfiguration: RabbitMQConfiguration? = null - private var http: Boolean = false - private var time: Boolean = false - private var configureRequestValidation: (RequestValidationConfig.() -> Unit)? = null - private var configureStatusPages: (StatusPagesConfig.() -> Unit)? = null - private var configureRouting: (Routing.() -> Unit)? = null - - fun addDIModule(diModule: DI.Module) { - diModules = diModules.plus(diModule) - } +class ApplicationConfiguration(block: Builder.() -> Unit) { + val di = DI { importAll(builder.diModules) } - fun addEventStore(eventStoreConfiguration: EventStoreConfiguration) { - this.eventStoreConfiguration = eventStoreConfiguration - } + private val builder: Builder = Builder().apply(block) - fun addMongoDB(mongoDBConfiguration: MongoDBConfiguration) { - this.mongoDBConfiguration = mongoDBConfiguration + fun applyTo(application: Application) { + application.apply { + di { extend(di) } + builder.mongoDBConfiguration?.let { runBlocking { configureMongoDB() } } + builder.eventStoreConfiguration?.let { configureEventStore(it) } + builder.rabbitMQConfiguration?.let { configureRabbitMQ(it) } + configureSerialization() + builder.configureRequestValidation?.let { configureRequestValidation(it) } + configureStatusPages(builder.configureStatusPages) + configureCallId() + configureCallLogging() + builder.configureRouting?.let { configureRouting(it) } + } } - fun addRabbitMQ(rabbitMQConfiguration: RabbitMQConfiguration) { - this.rabbitMQConfiguration = rabbitMQConfiguration - } + class Builder internal constructor() { + internal var diModules = listOf() + private set - fun addHttp() { - this.http = true - } + internal var eventStoreConfiguration: EventStoreConfiguration? = null + private set - fun addTime() { - this.time = true - } + internal var mongoDBConfiguration: MongoDBConfiguration? = null + private set - fun configureRequestValidation(configureRequestValidation: RequestValidationConfig.() -> Unit) { - this.configureRequestValidation = configureRequestValidation - } + internal var rabbitMQConfiguration: RabbitMQConfiguration? = null + private set - fun configureStatusPages(configureStatusPages: StatusPagesConfig.() -> Unit) { - this.configureStatusPages = configureStatusPages - } + internal var http: Boolean = false + private set - fun configureRouting(configureRouting: Routing.() -> Unit) { - this.configureRouting = configureRouting - } + internal var time: Boolean = false + private set - fun applyTo(application: Application) { - application.apply { - di { - eventStoreConfiguration?.let { import(eventStoreDependenciesModule(it)) } - mongoDBConfiguration?.let { import(mongoDBDependenciesModule(it)) } - rabbitMQConfiguration?.let { import(rabbitMQDependenciesModule(it)) } - if (http) { - import(httpDependenciesModule) - } - if (time) { - import(timeDependenciesModule) - } - importAll(diModules) - } - mongoDBConfiguration?.let { runBlocking { configureMongoDB() } } - eventStoreConfiguration?.let { configureEventStore(it) } - rabbitMQConfiguration?.let { configureRabbitMQ(it) } - configureSerialization() - configureRequestValidation?.let { configureRequestValidation(it) } - configureStatusPages(configureStatusPages) - configureCallId() - configureCallLogging() - configureRouting?.let { configureRouting(it) } + internal var configureRequestValidation: (RequestValidationConfig.() -> Unit)? = null + private set + + internal var configureStatusPages: (StatusPagesConfig.() -> Unit)? = null + private set + + internal var configureRouting: (Routing.() -> Unit)? = null + private set + + fun addDIModule(diModule: DI.Module) { + diModules = diModules.plus(diModule) + } + + fun addEventStore(eventStoreConfiguration: EventStoreConfiguration) { + this.eventStoreConfiguration = eventStoreConfiguration + } + + fun addMongoDB(mongoDBConfiguration: MongoDBConfiguration) { + this.mongoDBConfiguration = mongoDBConfiguration + } + + fun addRabbitMQ(rabbitMQConfiguration: RabbitMQConfiguration) { + this.rabbitMQConfiguration = rabbitMQConfiguration + } + + fun addHttp() { + this.http = true + } + + fun addTime() { + this.time = true + } + + fun configureRequestValidation( + configureRequestValidation: RequestValidationConfig.() -> Unit + ) { + this.configureRequestValidation = configureRequestValidation + } + + fun configureStatusPages(configureStatusPages: StatusPagesConfig.() -> Unit) { + this.configureStatusPages = configureStatusPages + } + + fun configureRouting(configureRouting: Routing.() -> Unit) { + this.configureRouting = configureRouting } } }