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
Prev Previous commit
Next Next commit
Add http and time to builder and refactor builder
connorwyatt committed Nov 4, 2023
commit 9b7eb67e03f25c2d21f396c677b0786f5b62adff
1 change: 1 addition & 0 deletions server/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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)
181 changes: 95 additions & 86 deletions server/src/main/kotlin/io/connorwyatt/common/server/Server.kt
Original file line number Diff line number Diff line change
@@ -3,13 +3,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.*
@@ -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<DI.Module>,
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<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)
}
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<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
)
}
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 {