mirror of
https://github.com/Dannecron/spring-boot-demo.git
synced 2025-12-25 16:22:35 +03:00
move producing to sub-project
This commit is contained in:
@@ -78,7 +78,9 @@ subprojects {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation(project(":edge-contracts"))
|
||||||
implementation(project(":db"))
|
implementation(project(":db"))
|
||||||
|
implementation(project(":edge-producing"))
|
||||||
implementation(project(":core"))
|
implementation(project(":core"))
|
||||||
implementation(project(":edge-consuming"))
|
implementation(project(":edge-consuming"))
|
||||||
|
|
||||||
@@ -104,7 +106,9 @@ dependencies {
|
|||||||
|
|
||||||
kover(project(":edge-consuming"))
|
kover(project(":edge-consuming"))
|
||||||
kover(project(":core"))
|
kover(project(":core"))
|
||||||
|
kover(project(":edge-producing"))
|
||||||
kover(project(":db"))
|
kover(project(":db"))
|
||||||
|
kover(project(":edge-contracts"))
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.bootJar {
|
tasks.bootJar {
|
||||||
|
|||||||
10
edge-producing/build.gradle.kts
Normal file
10
edge-producing/build.gradle.kts
Normal 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)
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.github.dannecron.demo.services.kafka.dto
|
package com.github.dannecron.demo.edgeproducing.dto
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@@ -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")
|
class InvalidArgumentException(argName: String): RuntimeException("invalid argument $argName")
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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.edgecontracts.validation.SchemaValidator
|
||||||
import com.github.dannecron.demo.services.kafka.dto.ProductDto
|
import com.github.dannecron.demo.edgeproducing.dto.ProductDto
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.encodeToJsonElement
|
import kotlinx.serialization.json.encodeToJsonElement
|
||||||
import org.springframework.cloud.stream.function.StreamBridge
|
import org.springframework.cloud.stream.function.StreamBridge
|
||||||
@@ -9,10 +9,10 @@ import org.springframework.messaging.support.MessageBuilder
|
|||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class ProducerImpl(
|
class ProductProducerImpl(
|
||||||
private val streamBridge: StreamBridge,
|
private val streamBridge: StreamBridge,
|
||||||
private val schemaValidator: SchemaValidator,
|
private val schemaValidator: SchemaValidator,
|
||||||
): Producer {
|
): ProductProducer {
|
||||||
private companion object {
|
private companion object {
|
||||||
private const val BINDING_NAME_PRODUCT_SYNC = "productSyncProducer"
|
private const val BINDING_NAME_PRODUCT_SYNC = "productSyncProducer"
|
||||||
private const val SCHEMA_KAFKA_PRODUCT_SYNC = "kafka-product-sync"
|
private const val SCHEMA_KAFKA_PRODUCT_SYNC = "kafka-product-sync"
|
||||||
@@ -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.edgecontracts.validation.SchemaValidator
|
||||||
import com.github.dannecron.demo.services.kafka.dto.ProductDto
|
import com.github.dannecron.demo.edgeproducing.dto.ProductDto
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.encodeToJsonElement
|
import kotlinx.serialization.json.encodeToJsonElement
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
import org.mockito.kotlin.any
|
import org.mockito.kotlin.any
|
||||||
import org.mockito.kotlin.argumentCaptor
|
import org.mockito.kotlin.argumentCaptor
|
||||||
import org.mockito.kotlin.eq
|
import org.mockito.kotlin.eq
|
||||||
@@ -17,12 +18,11 @@ import org.springframework.messaging.Message
|
|||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import kotlin.test.Test
|
|
||||||
|
|
||||||
class ProducerImplTest {
|
class ProductProducerImplTest {
|
||||||
private val streamBridge: StreamBridge = mock()
|
private val streamBridge: StreamBridge = mock()
|
||||||
private val schemaValidator: SchemaValidator = mock()
|
private val schemaValidator: SchemaValidator = mock()
|
||||||
private val producerImpl = ProducerImpl(
|
private val producerImpl = ProductProducerImpl(
|
||||||
streamBridge = streamBridge,
|
streamBridge = streamBridge,
|
||||||
schemaValidator = schemaValidator,
|
schemaValidator = schemaValidator,
|
||||||
)
|
)
|
||||||
@@ -5,3 +5,5 @@ rootProject.name = "demo"
|
|||||||
include("db")
|
include("db")
|
||||||
include("core")
|
include("core")
|
||||||
include("edge-consuming")
|
include("edge-consuming")
|
||||||
|
include("edge-producing")
|
||||||
|
include("edge-contracts")
|
||||||
|
|||||||
@@ -3,21 +3,18 @@ package com.github.dannecron.demo.config
|
|||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
import com.fasterxml.jackson.databind.SerializationFeature
|
import com.fasterxml.jackson.databind.SerializationFeature
|
||||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
|
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.HttpClientEngine
|
||||||
import io.ktor.client.engine.cio.CIO
|
import io.ktor.client.engine.cio.CIO
|
||||||
import io.micrometer.observation.ObservationRegistry
|
import io.micrometer.observation.ObservationRegistry
|
||||||
import io.micrometer.observation.aop.ObservedAspect
|
import io.micrometer.observation.aop.ObservedAspect
|
||||||
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter
|
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter
|
||||||
import org.springframework.beans.factory.annotation.Value
|
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.Bean
|
||||||
import org.springframework.context.annotation.Configuration
|
import org.springframework.context.annotation.Configuration
|
||||||
import com.github.dannecron.demo.services.neko.Client as NekoClient
|
import com.github.dannecron.demo.services.neko.Client as NekoClient
|
||||||
import com.github.dannecron.demo.services.neko.ClientImpl as NekoClientImpl
|
import com.github.dannecron.demo.services.neko.ClientImpl as NekoClientImpl
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableConfigurationProperties(ValidationProperties::class)
|
|
||||||
class AppConfig {
|
class AppConfig {
|
||||||
@Bean
|
@Bean
|
||||||
fun objectMapper(): ObjectMapper = ObjectMapper().apply {
|
fun objectMapper(): ObjectMapper = ObjectMapper().apply {
|
||||||
|
|||||||
@@ -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?)
|
|
||||||
}
|
|
||||||
@@ -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),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -1,15 +1,8 @@
|
|||||||
package com.github.dannecron.demo
|
package com.github.dannecron.demo
|
||||||
|
|
||||||
import com.github.dannecron.demo.core.config.properties.ValidationProperties
|
|
||||||
import org.springframework.boot.test.context.TestConfiguration
|
import org.springframework.boot.test.context.TestConfiguration
|
||||||
import org.springframework.context.annotation.Bean
|
|
||||||
|
|
||||||
open class BaseUnitTest {
|
open class BaseUnitTest {
|
||||||
@TestConfiguration
|
@TestConfiguration
|
||||||
class TestConfig {
|
class TestConfig
|
||||||
@Bean
|
|
||||||
fun validationProperties(): ValidationProperties = ValidationProperties(
|
|
||||||
schema = mapOf("kafka-product-sync" to "kafka/product/sync.json"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
Reference in New Issue
Block a user