Skip to content

Commit

Permalink
feat: use non-reactive stack (#40)
Browse files Browse the repository at this point in the history
  • Loading branch information
tomwwinter authored Oct 22, 2024
1 parent 005ba07 commit fe19e7d
Show file tree
Hide file tree
Showing 88 changed files with 2,424 additions and 2,128 deletions.
27 changes: 12 additions & 15 deletions application/aam-backend-service/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ plugins {
application
distribution
jacoco
kotlin("jvm") version "1.9.25"
kotlin("plugin.spring") version "1.9.25"
kotlin("plugin.jpa") version "1.9.25"
id("org.springframework.boot") version "3.3.4"
id("io.spring.dependency-management") version "1.1.6"
id("io.sentry.jvm.gradle") version "4.11.0"
id("org.jetbrains.kotlin.kapt") version "1.9.25"
kotlin("jvm") version "1.9.25"
kotlin("plugin.spring") version "1.9.22"
id("io.sentry.jvm.gradle") version "4.11.0"
}

group = "com.aam-digital"
Expand Down Expand Up @@ -35,26 +36,22 @@ repositories {

dependencies {
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-amqp")
implementation("org.springframework.boot:spring-boot-starter-cache")
implementation("org.springframework.boot:spring-boot-starter-security")
// implementation("org.springframework.boot:spring-boot-starter-cache")
implementation("org.springframework.boot:spring-boot-starter-validation")
implementation("org.springframework.boot:spring-boot-starter-webflux")

implementation("org.springframework.data:spring-data-r2dbc")

implementation("org.springframework.security:spring-security-oauth2-resource-server")
implementation("org.springframework.security:spring-security-oauth2-jose")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server")

implementation("org.apache.commons:commons-lang3")

implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("io.projectreactor.kotlin:reactor-kotlin-extensions")

implementation("io.micrometer:micrometer-tracing-bridge-brave")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")

implementation("io.r2dbc:r2dbc-h2")
runtimeOnly("org.postgresql:postgresql:42.7.4")
runtimeOnly("com.h2database:h2")

annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.aamdigital.aambackendservice
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.properties.ConfigurationPropertiesScan
import org.springframework.boot.runApplication
import org.springframework.data.jpa.repository.config.EnableJpaRepositories
import org.springframework.scheduling.annotation.EnableScheduling
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
Expand All @@ -11,6 +12,7 @@ import java.util.*
@SpringBootApplication
@ConfigurationPropertiesScan
@EnableScheduling
@EnableJpaRepositories
class Application

fun main(args: Array<String>) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.aamdigital.aambackendservice.auth.core

import reactor.core.publisher.Mono

data class TokenResponse(val token: String)


Expand All @@ -28,5 +26,5 @@ data class AuthConfig(
* Used for fetching access tokens for third party systems.
*/
interface AuthProvider {
fun fetchToken(authClientConfig: AuthConfig): Mono<TokenResponse>
fun fetchToken(authClientConfig: AuthConfig): TokenResponse
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package com.aamdigital.aambackendservice.auth.core

import com.aamdigital.aambackendservice.error.AamErrorCode
import com.aamdigital.aambackendservice.error.ExternalSystemException
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.ObjectMapper
import org.slf4j.LoggerFactory
import org.springframework.http.MediaType
import org.springframework.util.LinkedMultiValueMap
import org.springframework.web.reactive.function.BodyInserters
import org.springframework.web.reactive.function.client.WebClient
import reactor.core.publisher.Mono
import org.springframework.web.client.RestClient

data class KeycloakTokenResponse(
@JsonProperty("access_token") val accessToken: String,
Expand All @@ -21,17 +20,22 @@ data class KeycloakTokenResponse(
*
* Not related to authentication mechanics related to endpoints we provide to others.
*
* @property webClient The WebClient used for making HTTP requests.
* @property httpClient The RestClient used for making HTTP requests.
* @property objectMapper The ObjectMapper used for parsing JSON responses.
*/
class KeycloakAuthProvider(
val webClient: WebClient,
val httpClient: RestClient,
val objectMapper: ObjectMapper,
) : AuthProvider {

enum class KeycloakAuthProviderError : AamErrorCode {
EMPTY_RESPONSE,
RESPONSE_PARSING_ERROR,
}

private val logger = LoggerFactory.getLogger(javaClass)

override fun fetchToken(authClientConfig: AuthConfig): Mono<TokenResponse> {
override fun fetchToken(authClientConfig: AuthConfig): TokenResponse {
val formData = LinkedMultiValueMap(
mutableMapOf(
"client_id" to listOf(authClientConfig.clientId),
Expand All @@ -44,30 +48,43 @@ class KeycloakAuthProvider(
}
)

return webClient.post()
.uri(authClientConfig.tokenEndpoint)
.headers {
it.contentType = MediaType.APPLICATION_FORM_URLENCODED
}
.body(
BodyInserters.fromFormData(
return try {
val response = httpClient.post()
.uri(authClientConfig.tokenEndpoint)
.headers {
it.contentType = MediaType.APPLICATION_FORM_URLENCODED
}
.body(
formData
)
).exchangeToMono {
it.bodyToMono(String::class.java)
}.map {
parseResponse(it)
}.doOnError { logger.error(it.message, it) }
.retrieve()
.body(String::class.java)
parseResponse(response)
} catch (ex: ExternalSystemException) {
logger.error(ex.message, ex)
throw ex
}
}

private fun parseResponse(raw: String): TokenResponse {
private fun parseResponse(raw: String?): TokenResponse {
if (raw.isNullOrEmpty()) {
throw ExternalSystemException(
message = "Could not parse access token from KeycloakAuthProvider.",
code = KeycloakAuthProviderError.EMPTY_RESPONSE
)
}

try {
val keycloakTokenResponse = objectMapper.readValue(raw, KeycloakTokenResponse::class.java)
return TokenResponse(
token = keycloakTokenResponse.accessToken
)
} catch (e: Exception) {
throw ExternalSystemException("Could not parse access token from KeycloakAuthProvider", e)
} catch (ex: Exception) {
throw ExternalSystemException(
message = "Could not parse access token from KeycloakAuthProvider",
cause = ex,
code = KeycloakAuthProviderError.RESPONSE_PARSING_ERROR
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,24 @@ import com.aamdigital.aambackendservice.auth.core.AuthProvider
import com.aamdigital.aambackendservice.auth.core.KeycloakAuthProvider
import com.fasterxml.jackson.databind.ObjectMapper
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.client.reactive.ReactorClientHttpConnector
import org.springframework.web.reactive.function.client.WebClient
import reactor.netty.http.client.HttpClient


@ConfigurationProperties("aam-keycloak-client-configuration")
data class KeycloakConfiguration(
val maxInMemorySizeInMegaBytes: Int = 16,
)
import org.springframework.web.client.RestClient

@Configuration
class AuthConfiguration {
companion object {
const val MEGA_BYTES_MULTIPLIER = 1024 * 1024
}


@Bean(name = ["aam-keycloak-client"])
fun aamKeycloakWebClient(
configuration: KeycloakConfiguration,
): WebClient {
val clientBuilder =
WebClient.builder()
.codecs {
it.defaultCodecs()
.maxInMemorySize(configuration.maxInMemorySizeInMegaBytes * MEGA_BYTES_MULTIPLIER)
}

return clientBuilder.clientConnector(ReactorClientHttpConnector(HttpClient.create())).build()
fun aamKeycloakRestClient(
): RestClient {
val clientBuilder = RestClient.builder()
return clientBuilder.build()
}

@Bean(name = ["aam-keycloak"])
fun aamKeycloakAuthProvider(
@Qualifier("aam-keycloak-client") webClient: WebClient,
@Qualifier("aam-keycloak-client") webClient: RestClient,
objectMapper: ObjectMapper,
): AuthProvider =
KeycloakAuthProvider(webClient = webClient, objectMapper = objectMapper)

KeycloakAuthProvider(httpClient = webClient, objectMapper = objectMapper)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,65 +3,72 @@ package com.aamdigital.aambackendservice.couchdb.core
import com.aamdigital.aambackendservice.couchdb.dto.CouchDbChangesResponse
import com.aamdigital.aambackendservice.couchdb.dto.DocSuccess
import com.aamdigital.aambackendservice.couchdb.dto.FindResponse
import org.springframework.core.io.buffer.DataBuffer
import com.aamdigital.aambackendservice.error.ExternalSystemException
import com.aamdigital.aambackendservice.error.NotFoundException
import org.springframework.http.HttpHeaders
import org.springframework.util.MultiValueMap
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import java.io.InputStream
import java.io.InterruptedIOException
import java.util.*
import kotlin.reflect.KClass

interface CouchDbClient {
fun allDatabases(): Mono<List<String>>
fun changes(database: String, queryParams: MultiValueMap<String, String>): Mono<CouchDbChangesResponse>
fun allDatabases(): List<String>
fun changes(database: String, queryParams: MultiValueMap<String, String>): CouchDbChangesResponse

fun <T : Any> find(
database: String,
body: Map<String, Any>,
queryParams: MultiValueMap<String, String> = getEmptyQueryParams(),
kClass: KClass<T>
): Mono<FindResponse<T>>
): FindResponse<T>

fun headDatabaseDocument(
database: String,
documentId: String,
): Mono<HttpHeaders>
): HttpHeaders

@Throws(
NotFoundException::class,
ExternalSystemException::class,
InterruptedIOException::class
)
fun <T : Any> getDatabaseDocument(
database: String,
documentId: String,
queryParams: MultiValueMap<String, String> = getEmptyQueryParams(),
kClass: KClass<T>,
): Mono<T>
): T

fun putDatabaseDocument(
database: String,
documentId: String,
body: Any
): Mono<DocSuccess>
): DocSuccess

fun <T : Any> getPreviousDocRev(
database: String,
documentId: String,
rev: String,
kClass: KClass<T>,
): Mono<T>
): Optional<T>

fun headAttachment(
database: String,
documentId: String,
attachmentId: String,
): Mono<HttpHeaders>
): HttpHeaders

fun getAttachment(
database: String,
documentId: String,
attachmentId: String,
): Flux<DataBuffer>
): InputStream

fun putAttachment(
database: String,
documentId: String,
attachmentId: String,
file: Flux<DataBuffer>
): Mono<DocSuccess>
file: InputStream
): DocSuccess
}
Loading

0 comments on commit fe19e7d

Please sign in to comment.