move producing to sub-project

This commit is contained in:
Savosin Denis
2025-06-03 11:34:09 +07:00
parent 0c858b59b3
commit 1bda2e1d21
15 changed files with 39 additions and 176 deletions

View File

@@ -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 {

View File

@@ -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)
}

View File

@@ -1,4 +1,4 @@
package com.github.dannecron.demo.services.kafka.dto
package com.github.dannecron.demo.edgeproducing.dto
import kotlinx.serialization.Serializable

View File

@@ -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")

View File

@@ -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)
}

View File

@@ -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"

View File

@@ -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,
)

View File

@@ -5,3 +5,5 @@ rootProject.name = "demo"
include("db")
include("core")
include("edge-consuming")
include("edge-producing")
include("edge-contracts")

View File

@@ -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 {

View File

@@ -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?)
}

View File

@@ -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),
)
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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<ProductNotFoundException> {
productSyncService.syncToKafka(guid, null)
}
verify(productService, times(1)).findByGuid(guid)
verifyNoInteractions(producer)
}
}

View File

@@ -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