diff --git a/server/build.gradle.kts b/server/build.gradle.kts new file mode 100644 index 0000000..87c5a66 --- /dev/null +++ b/server/build.gradle.kts @@ -0,0 +1,19 @@ +dependencies { + implementation(project(":configuration")) + implementation(project(":eventstore")) + implementation(project(":http")) + implementation(project(":mongodb")) + implementation(project(":rabbitmq")) + implementation(project(":time")) + + 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.cio) + implementation(libraries.ktor.server.contentNegotiation) + implementation(libraries.ktor.server.core) + implementation(libraries.ktor.server.requestValidation) + implementation(libraries.ktor.server.statusPages) +} 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..b3c9fde --- /dev/null +++ b/server/src/main/kotlin/io/connorwyatt/common/server/ApplicationConfiguration.kt @@ -0,0 +1,103 @@ +package io.connorwyatt.common.server + +import io.connorwyatt.common.eventstore.configuration.EventStoreConfiguration +import io.connorwyatt.common.eventstore.ktor.configureEventStore +import io.connorwyatt.common.mongodb.configuration.MongoDBConfiguration +import io.connorwyatt.common.mongodb.ktor.configureMongoDB +import io.connorwyatt.common.rabbitmq.configuration.RabbitMQConfiguration +import io.connorwyatt.common.rabbitmq.ktor.configureRabbitMQ +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(block: Builder.() -> Unit) { + val di = DI { importAll(builder.diModules) } + + private val builder: Builder = Builder().apply(block) + + 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) } + } + } + + class Builder internal constructor() { + internal var diModules = listOf() + private set + + internal var eventStoreConfiguration: EventStoreConfiguration? = null + private set + + internal var mongoDBConfiguration: MongoDBConfiguration? = null + private set + + internal var rabbitMQConfiguration: RabbitMQConfiguration? = null + private set + + internal var http: Boolean = false + private set + + internal var time: Boolean = false + private set + + 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 + } + } +} 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 new file mode 100644 index 0000000..3c32b36 --- /dev/null +++ b/server/src/main/kotlin/io/connorwyatt/common/server/Server.kt @@ -0,0 +1,15 @@ +package io.connorwyatt.common.server + +import io.ktor.server.cio.* +import io.ktor.server.engine.* + +class Server(port: Int, private val applicationConfiguration: ApplicationConfiguration) { + private val applicationEngine = + embeddedServer(CIO, port = port, host = "localhost") { + applicationConfiguration.applyTo(this) + } + + fun start() { + applicationEngine.start(wait = true) + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index a677ba6..689f864 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-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-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