Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Server package #1

Merged
merged 9 commits into from
Nov 4, 2023
19 changes: 19 additions & 0 deletions server/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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)
}
Original file line number Diff line number Diff line change
@@ -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<DI.Module>()
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
}
}
}
Original file line number Diff line number Diff line change
@@ -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<RequestValidationException> { call, cause ->
call.response.headers.append(
HttpHeaders.ContentType,
ContentType.Application.ProblemJson.toString()
)
call.respond(HttpStatusCode.BadRequest, ValidationProblemResponse(cause.reasons))
}
exception<Throwable> { 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) }
}
15 changes: 15 additions & 0 deletions server/src/main/kotlin/io/connorwyatt/common/server/Server.kt
Original file line number Diff line number Diff line change
@@ -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)
}
}
12 changes: 12 additions & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ include(":http")
include(":mongodb")
include(":optional")
include(":rabbitmq")
include(":server")
include(":time")

pluginManagement {
Expand Down Expand Up @@ -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
Expand Down