diff --git a/build.gradle.kts b/build.gradle.kts index 254af7b..201d38e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -78,7 +78,9 @@ subprojects { } dependencies { + implementation(project(":edge-contracts")) implementation(project(":db")) + implementation(project(":edge-producing")) implementation(project(":core")) implementation(project(":edge-consuming")) @@ -104,7 +106,9 @@ dependencies { kover(project(":edge-consuming")) kover(project(":core")) + kover(project(":edge-producing")) kover(project(":db")) + kover(project(":edge-contracts")) } tasks.bootJar { diff --git a/edge-producing/build.gradle.kts b/edge-producing/build.gradle.kts new file mode 100644 index 0000000..f33db3d --- /dev/null +++ b/edge-producing/build.gradle.kts @@ -0,0 +1,10 @@ +dependencies { + implementation(project(":edge-contracts")) + + implementation(rootProject.libs.jackson.datatype.jsr) + implementation(rootProject.libs.jackson.module.kotlin) + implementation(rootProject.libs.spring.boot.starter.validation) + implementation(rootProject.libs.spring.cloud.starter.streamKafka) + + testImplementation(rootProject.libs.spring.cloud.streamTestBinder) +} diff --git a/src/main/kotlin/com/github/dannecron/demo/services/kafka/dto/ProductDto.kt b/edge-producing/src/main/kotlin/com/github/dannecron/demo/edgeproducing/dto/ProductDto.kt similarity index 84% rename from src/main/kotlin/com/github/dannecron/demo/services/kafka/dto/ProductDto.kt rename to edge-producing/src/main/kotlin/com/github/dannecron/demo/edgeproducing/dto/ProductDto.kt index 2fd4ff7..1257736 100644 --- a/src/main/kotlin/com/github/dannecron/demo/services/kafka/dto/ProductDto.kt +++ b/edge-producing/src/main/kotlin/com/github/dannecron/demo/edgeproducing/dto/ProductDto.kt @@ -1,4 +1,4 @@ -package com.github.dannecron.demo.services.kafka.dto +package com.github.dannecron.demo.edgeproducing.dto import kotlinx.serialization.Serializable diff --git a/src/main/kotlin/com/github/dannecron/demo/services/kafka/exceptions/InvalidArgumentException.kt b/edge-producing/src/main/kotlin/com/github/dannecron/demo/edgeproducing/exceptions/InvalidArgumentException.kt similarity index 61% rename from src/main/kotlin/com/github/dannecron/demo/services/kafka/exceptions/InvalidArgumentException.kt rename to edge-producing/src/main/kotlin/com/github/dannecron/demo/edgeproducing/exceptions/InvalidArgumentException.kt index a3072d5..11688fa 100644 --- a/src/main/kotlin/com/github/dannecron/demo/services/kafka/exceptions/InvalidArgumentException.kt +++ b/edge-producing/src/main/kotlin/com/github/dannecron/demo/edgeproducing/exceptions/InvalidArgumentException.kt @@ -1,3 +1,3 @@ -package com.github.dannecron.demo.services.kafka.exceptions +package com.github.dannecron.demo.edgeproducing.exceptions class InvalidArgumentException(argName: String): RuntimeException("invalid argument $argName") diff --git a/edge-producing/src/main/kotlin/com/github/dannecron/demo/edgeproducing/producer/ProductProducer.kt b/edge-producing/src/main/kotlin/com/github/dannecron/demo/edgeproducing/producer/ProductProducer.kt new file mode 100644 index 0000000..b4dd090 --- /dev/null +++ b/edge-producing/src/main/kotlin/com/github/dannecron/demo/edgeproducing/producer/ProductProducer.kt @@ -0,0 +1,9 @@ +package com.github.dannecron.demo.edgeproducing.producer + +import com.github.dannecron.demo.edgeproducing.dto.ProductDto +import com.github.dannecron.demo.edgeproducing.exceptions.InvalidArgumentException + +interface ProductProducer { + @Throws(InvalidArgumentException::class) + fun produceProductSync(product: ProductDto) +} diff --git a/src/main/kotlin/com/github/dannecron/demo/services/kafka/ProducerImpl.kt b/edge-producing/src/main/kotlin/com/github/dannecron/demo/edgeproducing/producer/ProductProducerImpl.kt similarity index 82% rename from src/main/kotlin/com/github/dannecron/demo/services/kafka/ProducerImpl.kt rename to edge-producing/src/main/kotlin/com/github/dannecron/demo/edgeproducing/producer/ProductProducerImpl.kt index 1779baf..2508859 100644 --- a/src/main/kotlin/com/github/dannecron/demo/services/kafka/ProducerImpl.kt +++ b/edge-producing/src/main/kotlin/com/github/dannecron/demo/edgeproducing/producer/ProductProducerImpl.kt @@ -1,7 +1,7 @@ -package com.github.dannecron.demo.services.kafka +package com.github.dannecron.demo.edgeproducing.producer -import com.github.dannecron.demo.core.services.validation.SchemaValidator -import com.github.dannecron.demo.services.kafka.dto.ProductDto +import com.github.dannecron.demo.edgecontracts.validation.SchemaValidator +import com.github.dannecron.demo.edgeproducing.dto.ProductDto import kotlinx.serialization.json.Json import kotlinx.serialization.json.encodeToJsonElement import org.springframework.cloud.stream.function.StreamBridge @@ -9,10 +9,10 @@ import org.springframework.messaging.support.MessageBuilder import org.springframework.stereotype.Service @Service -class ProducerImpl( +class ProductProducerImpl( private val streamBridge: StreamBridge, private val schemaValidator: SchemaValidator, -): Producer { +): ProductProducer { private companion object { private const val BINDING_NAME_PRODUCT_SYNC = "productSyncProducer" private const val SCHEMA_KAFKA_PRODUCT_SYNC = "kafka-product-sync" diff --git a/src/test/kotlin/com/github/dannecron/demo/services/kafka/ProducerImplTest.kt b/edge-producing/src/test/kotlin/com/github/dannecron/demo/edgeproducing/producer/ProductProducerImplTest.kt similarity index 87% rename from src/test/kotlin/com/github/dannecron/demo/services/kafka/ProducerImplTest.kt rename to edge-producing/src/test/kotlin/com/github/dannecron/demo/edgeproducing/producer/ProductProducerImplTest.kt index a808171..502a792 100644 --- a/src/test/kotlin/com/github/dannecron/demo/services/kafka/ProducerImplTest.kt +++ b/edge-producing/src/test/kotlin/com/github/dannecron/demo/edgeproducing/producer/ProductProducerImplTest.kt @@ -1,10 +1,11 @@ -package com.github.dannecron.demo.services.kafka +package com.github.dannecron.demo.edgeproducing.producer -import com.github.dannecron.demo.core.services.validation.SchemaValidator -import com.github.dannecron.demo.services.kafka.dto.ProductDto +import com.github.dannecron.demo.edgecontracts.validation.SchemaValidator +import com.github.dannecron.demo.edgeproducing.dto.ProductDto import kotlinx.serialization.json.Json import kotlinx.serialization.json.encodeToJsonElement import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test import org.mockito.kotlin.any import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.eq @@ -17,12 +18,11 @@ import org.springframework.messaging.Message import java.time.OffsetDateTime import java.time.format.DateTimeFormatter import java.util.UUID -import kotlin.test.Test -class ProducerImplTest { +class ProductProducerImplTest { private val streamBridge: StreamBridge = mock() private val schemaValidator: SchemaValidator = mock() - private val producerImpl = ProducerImpl( + private val producerImpl = ProductProducerImpl( streamBridge = streamBridge, schemaValidator = schemaValidator, ) diff --git a/settings.gradle.kts b/settings.gradle.kts index c54f066..9361e15 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -5,3 +5,5 @@ rootProject.name = "demo" include("db") include("core") include("edge-consuming") +include("edge-producing") +include("edge-contracts") diff --git a/src/main/kotlin/com/github/dannecron/demo/config/AppConfig.kt b/src/main/kotlin/com/github/dannecron/demo/config/AppConfig.kt index d55834d..c0562db 100644 --- a/src/main/kotlin/com/github/dannecron/demo/config/AppConfig.kt +++ b/src/main/kotlin/com/github/dannecron/demo/config/AppConfig.kt @@ -3,21 +3,18 @@ package com.github.dannecron.demo.config import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.SerializationFeature import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule -import com.github.dannecron.demo.core.config.properties.ValidationProperties import io.ktor.client.engine.HttpClientEngine import io.ktor.client.engine.cio.CIO import io.micrometer.observation.ObservationRegistry import io.micrometer.observation.aop.ObservedAspect import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter import org.springframework.beans.factory.annotation.Value -import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import com.github.dannecron.demo.services.neko.Client as NekoClient import com.github.dannecron.demo.services.neko.ClientImpl as NekoClientImpl @Configuration -@EnableConfigurationProperties(ValidationProperties::class) class AppConfig { @Bean fun objectMapper(): ObjectMapper = ObjectMapper().apply { diff --git a/src/main/kotlin/com/github/dannecron/demo/services/ProductSyncService.kt b/src/main/kotlin/com/github/dannecron/demo/services/ProductSyncService.kt deleted file mode 100644 index 95bbcca..0000000 --- a/src/main/kotlin/com/github/dannecron/demo/services/ProductSyncService.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.github.dannecron.demo.services - - -import com.github.dannecron.demo.core.exceptions.ProductNotFoundException -import com.github.dannecron.demo.services.kafka.exceptions.InvalidArgumentException -import java.util.UUID - -interface ProductSyncService { - - @Throws(ProductNotFoundException::class, InvalidArgumentException::class) - fun syncToKafka(guid: UUID, topic: String?) -} diff --git a/src/main/kotlin/com/github/dannecron/demo/services/ProductSyncServiceImpl.kt b/src/main/kotlin/com/github/dannecron/demo/services/ProductSyncServiceImpl.kt deleted file mode 100644 index 1959373..0000000 --- a/src/main/kotlin/com/github/dannecron/demo/services/ProductSyncServiceImpl.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.github.dannecron.demo.services - -import com.github.dannecron.demo.core.dto.Product -import com.github.dannecron.demo.core.exceptions.ProductNotFoundException -import com.github.dannecron.demo.core.services.product.ProductService -import com.github.dannecron.demo.services.kafka.Producer -import com.github.dannecron.demo.services.kafka.dto.ProductDto -import com.github.dannecron.demo.services.kafka.exceptions.InvalidArgumentException -import org.springframework.stereotype.Service -import java.time.format.DateTimeFormatter -import java.util.UUID - -@Service -class ProductSyncServiceImpl( - private val productService: ProductService, - private val producer: Producer, -) : ProductSyncService { - - @Throws(ProductNotFoundException::class, InvalidArgumentException::class) - override fun syncToKafka(guid: UUID, topic: String?) { - val product = productService.findByGuid(guid) ?: throw ProductNotFoundException() - - producer.produceProductSync(product.toKafkaDto()) - } - - private fun Product.toKafkaDto() = ProductDto( - id = id, - guid = guid.toString(), - name = name, - description = description, - price = price, - createdAt = createdAt.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME), - updatedAt = updatedAt?.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME), - deletedAt = deletedAt?.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME), - ) -} diff --git a/src/main/kotlin/com/github/dannecron/demo/services/kafka/Producer.kt b/src/main/kotlin/com/github/dannecron/demo/services/kafka/Producer.kt deleted file mode 100644 index 0b38fb1..0000000 --- a/src/main/kotlin/com/github/dannecron/demo/services/kafka/Producer.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.github.dannecron.demo.services.kafka - -import com.github.dannecron.demo.services.kafka.dto.ProductDto -import com.github.dannecron.demo.services.kafka.exceptions.InvalidArgumentException - -interface Producer { - @Throws(InvalidArgumentException::class) - fun produceProductSync(product: ProductDto) -} diff --git a/src/test/kotlin/com/github/dannecron/demo/BaseUnitTest.kt b/src/test/kotlin/com/github/dannecron/demo/BaseUnitTest.kt index 82ad12c..3ca23d5 100644 --- a/src/test/kotlin/com/github/dannecron/demo/BaseUnitTest.kt +++ b/src/test/kotlin/com/github/dannecron/demo/BaseUnitTest.kt @@ -1,15 +1,8 @@ package com.github.dannecron.demo -import com.github.dannecron.demo.core.config.properties.ValidationProperties import org.springframework.boot.test.context.TestConfiguration -import org.springframework.context.annotation.Bean open class BaseUnitTest { @TestConfiguration - class TestConfig { - @Bean - fun validationProperties(): ValidationProperties = ValidationProperties( - schema = mapOf("kafka-product-sync" to "kafka/product/sync.json"), - ) - } + class TestConfig } diff --git a/src/test/kotlin/com/github/dannecron/demo/services/ProductSyncServiceImplTest.kt b/src/test/kotlin/com/github/dannecron/demo/services/ProductSyncServiceImplTest.kt deleted file mode 100644 index c81ee2d..0000000 --- a/src/test/kotlin/com/github/dannecron/demo/services/ProductSyncServiceImplTest.kt +++ /dev/null @@ -1,74 +0,0 @@ -package com.github.dannecron.demo.services - -import com.github.dannecron.demo.core.dto.Product -import com.github.dannecron.demo.core.exceptions.ProductNotFoundException -import com.github.dannecron.demo.core.services.product.ProductService -import com.github.dannecron.demo.services.kafka.Producer -import com.github.dannecron.demo.services.kafka.dto.ProductDto -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import org.mockito.kotlin.any -import org.mockito.kotlin.doReturn -import org.mockito.kotlin.mock -import org.mockito.kotlin.times -import org.mockito.kotlin.verify -import org.mockito.kotlin.verifyNoInteractions -import org.mockito.kotlin.whenever -import java.time.OffsetDateTime -import java.time.format.DateTimeFormatter -import java.util.UUID - -class ProductSyncServiceImplTest { - private val productService: ProductService = mock() - private val producer: Producer = mock() - - private val productSyncService = ProductSyncServiceImpl( - productService = productService, - producer = producer - ) - - private val guid = UUID.randomUUID() - private val product = Product( - id = 123, - guid = guid, - name = "name", - description = "description", - price = 10050, - createdAt = OffsetDateTime.now().minusDays(1), - updatedAt = OffsetDateTime.now().minusHours(2), - deletedAt = null, - ) - - private val kafkaProductDto = ProductDto( - id = 123, - guid = guid.toString(), - name = "name", - description = "description", - price = 10050, - createdAt = product.createdAt.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME), - updatedAt = product.updatedAt!!.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME), - deletedAt = null, - ) - - @Test - fun `syncToKafka - success`() { - whenever(productService.findByGuid(any())).thenReturn(product) - - productSyncService.syncToKafka(guid, null) - - verify(productService, times(1)).findByGuid(guid) - verify(producer, times(1)).produceProductSync(kafkaProductDto) - } - - @Test - fun `syncToKafka - not found`() { - whenever(productService.findByGuid(any())) doReturn null - - assertThrows { - productSyncService.syncToKafka(guid, null) - } - - verify(productService, times(1)).findByGuid(guid) - verifyNoInteractions(producer) - } -} diff --git a/src/test/resources/application_kfk.yml b/src/test/resources/application_kfk.yml deleted file mode 100644 index 81ae94d..0000000 --- a/src/test/resources/application_kfk.yml +++ /dev/null @@ -1,21 +0,0 @@ ---- -spring: - datasource: - url: jdbc:tc:postgresql:14-alpine:///test - hikari: - maximum-pool-size: 2 - driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver - jpa: - hibernate: - ddl-auto: create - -kafka: - bootstrap-servers: localhost:3392 - producer: - product: - default-sync-topic: demo-product-sync - consumer: - group-id: demo-consumer - topics: demo-city-sync - auto-offset-reset: earliest - auto-startup: true \ No newline at end of file