@@ -14,16 +14,21 @@ import kotlinx.coroutines.Job
1414import kotlinx.coroutines.delay
1515import kotlinx.coroutines.isActive
1616import kotlinx.coroutines.launch
17+ import org.apache.kafka.clients.admin.AdminClient
18+ import org.apache.kafka.clients.admin.AdminClientConfig
19+ import org.apache.kafka.clients.admin.NewTopic
1720import org.apache.kafka.clients.producer.KafkaProducer
1821import org.apache.kafka.clients.producer.Producer
1922import org.apache.kafka.clients.producer.ProducerConfig.BOOTSTRAP_SERVERS_CONFIG
2023import org.apache.kafka.clients.producer.ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG
2124import org.apache.kafka.clients.producer.ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG
2225import org.apache.kafka.clients.producer.ProducerRecord
26+ import org.apache.kafka.common.errors.TopicExistsException
2327import org.testcontainers.containers.KafkaContainer
2428import org.testcontainers.utility.DockerImageName
2529import reactor.core.publisher.Flux
2630import java.util.*
31+ import java.util.concurrent.TimeUnit
2732
2833val StackRunner .kafka: List <KafkaExecutor >
2934 get() {
@@ -60,6 +65,9 @@ class KafkaExecutor(private val config: KafkaConfig) : InfrastructureComponent<K
6065 val bootstrapServers = kafkaContainer.bootstrapServers
6166 logger.info { " Kafka container started - bootstrap servers: $bootstrapServers " }
6267
68+ // Create topics with specified partitions
69+ createTopics(bootstrapServers, config.producers)
70+
6371 config.producers.forEach { producerConfig ->
6472 val producer = createKafkaProducer(bootstrapServers, producerConfig)
6573 producers.add(producer)
@@ -131,6 +139,33 @@ class KafkaExecutor(private val config: KafkaConfig) : InfrastructureComponent<K
131139 }
132140 return KafkaProducer (props)
133141 }
142+
143+ private fun createTopics (bootstrapServers : String , producers : List <ProducerConfig >) {
144+ val adminProps = Properties ().apply {
145+ put(AdminClientConfig .BOOTSTRAP_SERVERS_CONFIG , bootstrapServers)
146+ }
147+
148+ AdminClient .create(adminProps).use { adminClient ->
149+ // Group topics by name to handle duplicates and use max partitions
150+ val topicConfigs = producers.groupBy { it.topic }
151+ .mapValues { (_, configs) -> configs.maxOf { it.partitions } }
152+ .map { (topicName, partitions) ->
153+ NewTopic (topicName, partitions, 1 .toShort()) // replication factor of 1 for single broker
154+ }
155+
156+ if (topicConfigs.isNotEmpty()) {
157+ try {
158+ val result = adminClient.createTopics(topicConfigs)
159+ result.all().get(30 , TimeUnit .SECONDS ) // Wait for completion with timeout
160+ logger.info { " Created topics: ${topicConfigs.map { " ${it.name()} (${it.numPartitions()} partitions)" }} " }
161+ } catch (e: TopicExistsException ) {
162+ logger.warn(e) { " Some topics already exist: ${e.message} " }
163+ } catch (e: Exception ) {
164+ logger.error(e) { " Failed to create topics due to unexpected error: ${e.message} " }
165+ }
166+ }
167+ }
168+ }
134169}
135170
136171data class KafkaContainerConfig (val bootstrapServers : String )
0 commit comments