Skip to content

Commit

Permalink
Refactor to make the solution work with the test application
Browse files Browse the repository at this point in the history
  • Loading branch information
connorwyatt committed Nov 4, 2023
1 parent 82144cf commit 29d2b22
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 176 deletions.
Original file line number Diff line number Diff line change
@@ -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<DI.Module>()
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) }
}
}
}
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) }
}
182 changes: 6 additions & 176 deletions server/src/main/kotlin/io/connorwyatt/common/server/Server.kt
Original file line number Diff line number Diff line change
@@ -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<DI.Module>()
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<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 {
fun build(builder: Builder.() -> Unit): Server =
Builder().apply { builder.invoke(this) }.build()
fun start() {
applicationEngine.start(wait = true)
}
}

0 comments on commit 29d2b22

Please sign in to comment.