diff --git a/README.md b/README.md index 91ad047..7dac5a3 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ In this project an API rest with Ktot, Arrow, JDBC was created. ## Requirements -* JDK >= 17 +* JDK >= 21 * Kotlin * Docker * Docker Compose diff --git a/build.gradle b/build.gradle index b6007ea..536db28 100644 --- a/build.gradle +++ b/build.gradle @@ -29,6 +29,7 @@ allprojects { } kotlin { + jvmToolchain(21) compilerOptions { apiVersion = KotlinVersion.KOTLIN_1_9 } @@ -42,6 +43,7 @@ allprojects { } } + tasks.withType(KotlinCompile).configureEach { kotlinOptions { verbose = true @@ -95,7 +97,7 @@ subprojects { def groovy_version = "3.0.9" buildscript { - ext.kotlin_version = '1.9.20' + ext.kotlin_version = '1.9.21' repositories { mavenCentral() } diff --git a/infrastructure/http/src/main/kotlin/com/leysoft/infrastructure/http/extension.kt b/infrastructure/http/src/main/kotlin/com/leysoft/infrastructure/http/extension.kt index 8ceeb0d..c025d43 100644 --- a/infrastructure/http/src/main/kotlin/com/leysoft/infrastructure/http/extension.kt +++ b/infrastructure/http/src/main/kotlin/com/leysoft/infrastructure/http/extension.kt @@ -1,7 +1,7 @@ package com.leysoft.infrastructure.http -import arrow.core.Either -import arrow.core.Option +import arrow.core.* +import com.leysoft.core.error.BaseException import com.leysoft.core.error.InfrastructureException import io.ktor.http.* import io.ktor.server.application.* @@ -30,5 +30,11 @@ suspend inline fun ApplicationCall.respondJson( fun ApplicationCall.getParam(key: String): Option = Option.fromNullable(parameters[key]) +fun ApplicationCall.getRequiredParam(key: String, f: (String) -> A): Either = + when (val id = getParam(key)) { + is Some -> f(id.value).right() + else -> RequiredParameterException(key).left() + } + data class RequiredParameterException(override val message: String) : InfrastructureException(message) diff --git a/infrastructure/jdbc/src/main/kotlin/com/leysoft/infrastructure/jdbc/Jdbc.kt b/infrastructure/jdbc/src/main/kotlin/com/leysoft/infrastructure/jdbc/Jdbc.kt index 46fbf16..da8ff2b 100644 --- a/infrastructure/jdbc/src/main/kotlin/com/leysoft/infrastructure/jdbc/Jdbc.kt +++ b/infrastructure/jdbc/src/main/kotlin/com/leysoft/infrastructure/jdbc/Jdbc.kt @@ -58,7 +58,8 @@ interface Jdbc { decoder: Decoder ): Either = withContext(this@CoroutineContext) { - Either.catch { first(query) { decoder.decode(it) } } + log.info("Start Jdbc.first(): ${query.statement}") + either { first(query) { decoder.decode(it) } } .mapLeft { Data.QueryError( it.message ?: "Error trying to execute first" @@ -67,19 +68,20 @@ interface Jdbc { .flatMap { it?.right() ?: Data.SqlNotFound("Not Found").left() } + .onLeft { error -> log.info("Error executing Jdbc.first(): $error") } + .onRight { log.info("End Jdbc.first(): ${query.statement}") } } override suspend fun list(query: SqlQuery, decoder: Decoder): Either> = withContext(this@CoroutineContext) { - log.info("Start Jdbc.list()") - val result = either> { this@Session.list(query) { decoder.decode(it) } } + log.info("Start Jdbc.list(): ${query.statement}") + either> { this@Session.list(query) { decoder.decode(it) } } .mapLeft { Data.QueryError( it.message ?: "Error trying to execute list" ) - } - log.info("End Jdbc.list()") - result + }.onLeft { error -> log.info("Error executing Jdbc.list(): $error") } + .onRight { log.info("End Jdbc.list(): ${query.statement}") } } override suspend fun command(command: SqlQuery): Either = @@ -90,12 +92,15 @@ interface Jdbc { override suspend fun transaction(program: (Transaction) -> A): Either = withContext(this@CoroutineContext) { + log.info("Start Jdbc.transaction()") either { this@Session.transaction { program(it) } } .mapLeft { Data.TransactionError( it.message ?: "Error trying to execute transaction" ) } + .onLeft { error -> log.info("Error executing Jdbc.transaction(): $error") } + .onRight { log.info("End Jdbc.transaction()") } } } } diff --git a/infrastructure/logger/src/main/resources/logback.xml b/infrastructure/logger/src/main/resources/logback.xml index 7735885..40338ff 100644 --- a/infrastructure/logger/src/main/resources/logback.xml +++ b/infrastructure/logger/src/main/resources/logback.xml @@ -28,6 +28,7 @@ + diff --git a/products/adapter/src/main/kotlin/com/leysoft/products/adapter/in/api/routes.kt b/products/adapter/src/main/kotlin/com/leysoft/products/adapter/in/api/routes.kt index b901d51..bda3d40 100644 --- a/products/adapter/src/main/kotlin/com/leysoft/products/adapter/in/api/routes.kt +++ b/products/adapter/src/main/kotlin/com/leysoft/products/adapter/in/api/routes.kt @@ -1,9 +1,7 @@ package com.leysoft.products.adapter.`in`.api -import arrow.core.Either -import arrow.core.Some +import arrow.core.flatMap import com.leysoft.core.domain.toProductId -import com.leysoft.core.error.BaseException import com.leysoft.infrastructure.http.* import com.leysoft.products.application.ProductService import io.ktor.http.* @@ -34,13 +32,12 @@ private fun Route.all(service: ProductService) { private fun Route.get(service: ProductService) { get("/{id}") { - val result = when (val id = call.getParam("id")) { - is Some -> service.getBy(id.value.toProductId()) - else -> Either.Left(RequiredParameterException("id")) - } - result.handle({ - call.respondJson(HttpStatusCode.OK, it) - }) { errorHandler(call) } + call.getRequiredParam("id") { it } + .map { it.toProductId() } + .flatMap { service.getBy(it) } + .handle({ + call.respondJson(HttpStatusCode.OK, it) + }) { errorHandler(call) } } } @@ -56,12 +53,11 @@ private fun Route.create(service: ProductService) { private fun Route.delete(service: ProductService) { delete("/{id}") { - val result = when (val id = call.getParam("id")) { - is Some -> service.deleteBy(id.value.toProductId()) - else -> Either.Left(RequiredParameterException("id")) - } - result.handle({ - call.response.status(HttpStatusCode.Accepted) - }) { errorHandler(call) } + call.getRequiredParam("id") { it } + .map { it.toProductId() } + .flatMap { service.deleteBy(it) } + .handle({ + call.response.status(HttpStatusCode.Accepted) + }) { errorHandler(call) } } } diff --git a/products/adapter/src/main/kotlin/com/leysoft/products/adapter/out/persistence/memory/InMemoryProductRepository.kt b/products/adapter/src/main/kotlin/com/leysoft/products/adapter/out/persistence/memory/InMemoryProductRepository.kt index 6d51150..1d6535c 100644 --- a/products/adapter/src/main/kotlin/com/leysoft/products/adapter/out/persistence/memory/InMemoryProductRepository.kt +++ b/products/adapter/src/main/kotlin/com/leysoft/products/adapter/out/persistence/memory/InMemoryProductRepository.kt @@ -2,6 +2,8 @@ package com.leysoft.products.adapter.out.persistence.memory import arrow.core.Either import arrow.core.flatMap +import arrow.core.left +import arrow.core.raise.either import arrow.core.right import arrow.fx.coroutines.Atomic import com.leysoft.core.error.CreateProductException @@ -18,33 +20,26 @@ class InMemoryProductRepository private constructor(private val storage: Storage ProductRepository { override suspend fun findBy(id: ProductId): Either = - Either.catch({ - NotFoundProductException("Not found product: $id") - }) { storage.get() } + either> { storage.get() } + .mapLeft { NotFoundProductException("Not found product: $id") } .map { it[id.value] } - .flatMap { it?.right() ?: Either.Left(NotFoundProductException("Not found product: $id")) } + .flatMap { it?.right() ?: NotFoundProductException("Not found product: $id").left() } override suspend fun findAll(): Either> = - Either.catch({ NotFoundProductException("Not found products") }) { + either> { storage.get().values.toList() - } + }.mapLeft { NotFoundProductException("Not found products") } override suspend fun save(product: Product): Either { return when (val result = findBy(product.id)) { is Either.Left -> when (val error = result.value) { - is NotFoundProductException -> Either.catch({ - CreateProductException("Not save Product: ${product.id}") - }) { - storage.update { - it.plus(Pair(product.id.value, product)) - } - } - else -> Either.Left(error) + is NotFoundProductException -> either { + storage.update { it.plus(Pair(product.id.value, product)) } + }.mapLeft { CreateProductException("Not save Product: ${product.id}") } + else -> error.left() } - else -> Either.Left( - CreateProductException("Not save Product: ${product.id}") - ) + else -> CreateProductException("Not save Product: ${product.id}").left() } } @@ -55,7 +50,7 @@ class InMemoryProductRepository private constructor(private val storage: Storage storage.update { it.minus(id.value) } } } - is Either.Left -> Either.Left(result.value) + is Either.Left -> result.value.left() } } diff --git a/products/application/src/main/kotlin/com/leysoft/products/application/ProductService.kt b/products/application/src/main/kotlin/com/leysoft/products/application/ProductService.kt index a094aed..8552bc6 100644 --- a/products/application/src/main/kotlin/com/leysoft/products/application/ProductService.kt +++ b/products/application/src/main/kotlin/com/leysoft/products/application/ProductService.kt @@ -1,6 +1,8 @@ package com.leysoft.products.application import arrow.core.Either +import arrow.core.flatMap +import arrow.core.right import com.leysoft.core.domain.Product import com.leysoft.core.domain.ProductId import com.leysoft.core.error.ProductException @@ -20,7 +22,8 @@ interface ProductService { companion object Instance { fun make(repository: ProductRepository): ProductService = object : ProductService { override suspend fun getBy(id: ProductId): Either = - repository.findBy(id.fromCore()) + id.fromCore().right() + .flatMap { repository.findBy(it) } .map { it.toCore() } override suspend fun getAll(): Either> =