public class Application implements ApplicationRunner
{
@Autowired
- ApplicationRecordHandler recordHandler;
+ DriverController driverController;
@Override
public void run(ApplicationArguments args) throws Exception
{
log.info("Starting EndlessConsumer");
- recordHandler.start();
+ driverController.start();
}
@PreDestroy
try
{
log.info("Stopping EndlessConsumer");
- recordHandler.stop();
+ driverController.stop();
}
catch (IllegalStateException e)
{
@Bean
public ApplicationRecordHandler applicationRecordHandler(
AdderResults adderResults,
- KafkaListenerEndpointRegistry endpointRegistry,
- ApplicationErrorHandler errorHandler,
KafkaProperties kafkaProperties,
ApplicationProperties applicationProperties)
{
return new ApplicationRecordHandler(
adderResults,
- endpointRegistry,
- errorHandler,
Optional.ofNullable(applicationProperties.getThrottle()),
kafkaProperties.getClientId());
}
{
return new ApplicationErrorHandler();
}
+
+ @Bean
+ public DriverController driverController(
+ KafkaProperties kafkaProperties,
+ ApplicationRecordHandler applicationRecordHandler,
+ AdderResults adderResults,
+ KafkaListenerEndpointRegistry endpointRegistry,
+ ApplicationErrorHandler errorHandler)
+ {
+ return new DriverController(
+ kafkaProperties.getClientId(),
+ applicationRecordHandler,
+ adderResults,
+ endpointRegistry,
+ errorHandler);
+ }
}
@RequiredArgsConstructor
public class ApplicationHealthIndicator implements HealthIndicator
{
- private final ApplicationRecordHandler consumer;
+ private final DriverController driverController;
@Override
{
try
{
- return consumer
+ return driverController
.exitStatus()
.map(Health::down)
.orElse(Health.outOfService())
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.consumer.ConsumerRecord;
-import org.springframework.kafka.config.KafkaListenerEndpointRegistry;
+import org.springframework.kafka.annotation.KafkaListener;
import java.time.Duration;
import java.util.HashMap;
@RequiredArgsConstructor
@Slf4j
+@KafkaListener(
+ id = "${spring.kafka.client-id}",
+ idIsGroup = false,
+ topics = "${sumup.adder.topic}",
+ batch = "true",
+ autoStartup = "false")
public class ApplicationRecordHandler implements RecordHandler<String, Message>
{
private final AdderResults results;
- private final KafkaListenerEndpointRegistry registry;
- private final ApplicationErrorHandler errorHandler;
private final Optional<Duration> throttle;
private final String id;
{
return state.get(partition);
}
-
-
-
- public void start()
- {
- if (running())
- throw new IllegalStateException("Consumer instance " + id + " is already running!");
-
- log.info("{} - Starting", id);
- errorHandler.clearState();
- registry.getListenerContainer(id).start();
- }
-
- public void stop()
- {
- if (running())
- throw new IllegalStateException("Consumer instance " + id + " is not running!");
-
- log.info("{} - Stopping", id);
- registry.getListenerContainer(id).stop();
- log.info("{} - Stopped", id);
- }
-
- public boolean running()
- {
- return registry.getListenerContainer(id).isRunning();
- }
-
- public Optional<Exception> exitStatus()
- {
- if (running())
- throw new IllegalStateException("No exit-status available: Consumer instance " + id + " is running!");
-
- return errorHandler.getException();
- }
}
package de.juplo.kafka;
import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
+import org.springframework.kafka.config.KafkaListenerEndpointRegistry;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.stream.Collectors;
-@RestController
+@ResponseBody
@RequiredArgsConstructor
+@Slf4j
public class DriverController
{
+ private final String id;
private final ApplicationRecordHandler recordHandler;
private final AdderResults results;
+ private final KafkaListenerEndpointRegistry registry;
+ private final ApplicationErrorHandler errorHandler;
@PostMapping("start")
public void start()
{
- recordHandler.start();
+ if (running())
+ throw new IllegalStateException("Consumer instance " + id + " is already running!");
+
+ log.info("{} - Starting", id);
+ errorHandler.clearState();
+ registry.getListenerContainer(id).start();
}
@PostMapping("stop")
public void stop() throws ExecutionException, InterruptedException
{
- recordHandler.stop();
+ if (running())
+ throw new IllegalStateException("Consumer instance " + id + " is not running!");
+
+ log.info("{} - Stopping", id);
+ registry.getListenerContainer(id).stop();
+ log.info("{} - Stopped", id);
+ }
+
+ public boolean running()
+ {
+ return registry.getListenerContainer(id).isRunning();
+ }
+
+ public Optional<Exception> exitStatus()
+ {
+ if (running())
+ throw new IllegalStateException("No exit-status available: Consumer instance " + id + " is running!");
+
+ return errorHandler.getException();
}
import com.mongodb.client.MongoClient;
import lombok.extern.slf4j.Slf4j;
-import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
@Autowired
TestRecordHandler<K, V> recordHandler;
@Autowired
- EndlessConsumer<K, V> endlessConsumer;
+ DriverController driverController;
KafkaProducer<Bytes, Bytes> testRecordProducer;
KafkaConsumer<Bytes, Bytes> offsetConsumer;
});
assertThatExceptionOfType(IllegalStateException.class)
- .isThrownBy(() -> endlessConsumer.exitStatus())
+ .isThrownBy(() -> driverController.exitStatus())
.describedAs("Consumer should still be running");
- endlessConsumer.stop();
+ driverController.stop();
recordGenerator.assertBusinessLogic();
}
await("Consumer failed")
.atMost(Duration.ofSeconds(30))
.pollInterval(Duration.ofSeconds(1))
- .until(() -> !endlessConsumer.running());
+ .until(() -> !driverController.running());
checkSeenOffsetsForProgress();
assertSeenOffsetsEqualCommittedOffsets(recordHandler.seenOffsets);
- endlessConsumer.start();
+ driverController.start();
await("Consumer failed")
.atMost(Duration.ofSeconds(30))
.pollInterval(Duration.ofSeconds(1))
- .until(() -> !endlessConsumer.running());
+ .until(() -> !driverController.running());
checkSeenOffsetsForProgress();
assertSeenOffsetsEqualCommittedOffsets(recordHandler.seenOffsets);
assertThatNoException()
.describedAs("Consumer should not be running")
- .isThrownBy(() -> endlessConsumer.exitStatus());
- assertThat(endlessConsumer.exitStatus())
+ .isThrownBy(() -> driverController.exitStatus());
+ assertThat(driverController.exitStatus())
.describedAs("Consumer should have exited abnormally")
.containsInstanceOf(RecordDeserializationException.class);
await("Consumer failed")
.atMost(Duration.ofSeconds(30))
.pollInterval(Duration.ofSeconds(1))
- .until(() -> !endlessConsumer.running());
+ .until(() -> !driverController.running());
checkSeenOffsetsForProgress();
assertSeenOffsetsAreBehindCommittedOffsets(recordHandler.seenOffsets);
- endlessConsumer.start();
+ driverController.start();
await("Consumer failed")
.atMost(Duration.ofSeconds(30))
.pollInterval(Duration.ofSeconds(1))
- .until(() -> !endlessConsumer.running());
+ .until(() -> !driverController.running());
assertSeenOffsetsAreBehindCommittedOffsets(recordHandler.seenOffsets);
assertThatNoException()
.describedAs("Consumer should not be running")
- .isThrownBy(() -> endlessConsumer.exitStatus());
- assertThat(endlessConsumer.exitStatus())
+ .isThrownBy(() -> driverController.exitStatus());
+ assertThat(driverController.exitStatus())
.describedAs("Consumer should have exited abnormally")
.containsInstanceOf(RuntimeException.class);
recordHandler.seenOffsets.put(tp, offset - 1);
});
- endlessConsumer.start();
+ driverController.start();
}
@AfterEach
{
try
{
- endlessConsumer.stop();
+ driverController.stop();
}
catch (Exception e)
{
}
@Bean(destroyMethod = "close")
- public org.apache.kafka.clients.consumer.Consumer<String, Message> kafkaConsumer(ConsumerFactory<String, Message> factory)
+ public org.apache.kafka.clients.consumer.Consumer<String, Message> kafkaConsumer(ConsumerFactory<String, Message> factory)
{
return factory.createConsumer();
}