From 594779ee77653b937f429da68250ce71b2ebc2e0 Mon Sep 17 00:00:00 2001 From: Soby Chacko Date: Thu, 24 Oct 2019 18:12:54 -0400 Subject: [PATCH] Cleaning up samples Kotlin e2e sample with Boot 2.2.0.RELEASE Kafka Streams word count functional updates --- .../customer-service/pom.xml | 2 +- kafka-e2e-kotlin-sample/order-service/pom.xml | 2 +- .../shipping-service/pom.xml | 2 +- .../kafka-streams-word-count/README.adoc | 9 +- .../kafka-streams-word-count/pom.xml | 93 ++++++++++++++++-- .../KafkaStreamsWordCountApplication.java | 27 +++--- .../streams/word/count/SampleRunner.java | 94 ------------------- .../src/main/resources/application.yml | 46 +++++---- ...KafkaStreamsWordCountApplicationTests.java | 33 ++++--- .../WordCountProcessorApplicationTests.java | 9 +- 10 files changed, 161 insertions(+), 156 deletions(-) delete mode 100644 kafka-streams-samples/kafka-streams-word-count/src/main/java/kafka/streams/word/count/SampleRunner.java diff --git a/kafka-e2e-kotlin-sample/customer-service/pom.xml b/kafka-e2e-kotlin-sample/customer-service/pom.xml index 7da8680..0878b55 100644 --- a/kafka-e2e-kotlin-sample/customer-service/pom.xml +++ b/kafka-e2e-kotlin-sample/customer-service/pom.xml @@ -11,7 +11,7 @@ org.springframework.boot spring-boot-starter-parent - 2.2.0.BUILD-SNAPSHOT + 2.2.0.RELEASE diff --git a/kafka-e2e-kotlin-sample/order-service/pom.xml b/kafka-e2e-kotlin-sample/order-service/pom.xml index 2cf1eaa..529705e 100644 --- a/kafka-e2e-kotlin-sample/order-service/pom.xml +++ b/kafka-e2e-kotlin-sample/order-service/pom.xml @@ -11,7 +11,7 @@ org.springframework.boot spring-boot-starter-parent - 2.2.0.BUILD-SNAPSHOT + 2.2.0.RELEASE diff --git a/kafka-e2e-kotlin-sample/shipping-service/pom.xml b/kafka-e2e-kotlin-sample/shipping-service/pom.xml index c41a6ab..8582ec9 100644 --- a/kafka-e2e-kotlin-sample/shipping-service/pom.xml +++ b/kafka-e2e-kotlin-sample/shipping-service/pom.xml @@ -11,7 +11,7 @@ org.springframework.boot spring-boot-starter-parent - 2.2.0.BUILD-SNAPSHOT + 2.2.0.RELEASE diff --git a/kafka-streams-samples/kafka-streams-word-count/README.adoc b/kafka-streams-samples/kafka-streams-word-count/README.adoc index e6e1e4e..61e5ffc 100644 --- a/kafka-streams-samples/kafka-streams-word-count/README.adoc +++ b/kafka-streams-samples/kafka-streams-word-count/README.adoc @@ -5,7 +5,6 @@ This is an example of a Spring Cloud Stream processor using Kafka Streams suppor The example is based on the word count application from the https://github.com/confluentinc/examples/blob/3.2.x/kafka-streams/src/main/java/io/confluent/examples/streams/WordCountLambdaExample.java[reference documentation]. It uses a single input and a single output. In essence, the application receives text messages from an input topic and computes word occurrence counts in a configurable time window and report that in an output topic. -This sample uses lambda expressions and thus requires Java 8+. The sample uses a default timewindow of 30 seconds. === Running the app: @@ -24,10 +23,18 @@ Issue the following commands: `docker exec -it kafka-wordcount /opt/kafka/bin/kafka-console-producer.sh --broker-list 127.0.0.1:9092 --topic words` +Or if you prefer `kafkacat`: + +`kafkacat -b localhost:9092 -t words -P` + On another terminal: `docker exec -it kafka-wordcount /opt/kafka/bin/kafka-console-consumer.sh --bootstrap-server 127.0.0.1:9092 --topic counts` +Or if you prefer `kafkacat`: + +`kafkacat -b localhost:9092 -t counts` + Enter some text in the console producer and watch the output in the console consumer. Once you are done, stop the Kafka cluster: `docker-compose down` \ No newline at end of file diff --git a/kafka-streams-samples/kafka-streams-word-count/pom.xml b/kafka-streams-samples/kafka-streams-word-count/pom.xml index 8899666..8bd0bc8 100644 --- a/kafka-streams-samples/kafka-streams-word-count/pom.xml +++ b/kafka-streams-samples/kafka-streams-word-count/pom.xml @@ -6,26 +6,37 @@ kafka-streams-word-count 0.0.1-SNAPSHOT jar - kafka-streams-word-count Demo project for Spring Boot - io.spring.cloud.stream.sample - spring-cloud-stream-samples-parent - 0.0.1-SNAPSHOT - ../.. + org.springframework.boot + spring-boot-starter-parent + 2.2.0.RELEASE + + + Hoxton.BUILD-SNAPSHOT + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + org.springframework.cloud spring-cloud-stream-binder-kafka-streams - - org.springframework.cloud - spring-cloud-stream-binder-kafka - org.springframework.boot spring-boot-starter-test @@ -42,6 +53,19 @@ ${kafka.version} test + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-web + @@ -53,4 +77,55 @@ + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/libs-snapshot-local + + true + + + false + + + + spring-milestones + Spring Milestones + https://repo.spring.io/libs-milestone-local + + false + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/libs-snapshot-local + + true + + + false + + + + spring-milestones + Spring Milestones + https://repo.spring.io/libs-milestone-local + + false + + + + spring-releases + Spring Releases + https://repo.spring.io/libs-release-local + + false + + + + diff --git a/kafka-streams-samples/kafka-streams-word-count/src/main/java/kafka/streams/word/count/KafkaStreamsWordCountApplication.java b/kafka-streams-samples/kafka-streams-word-count/src/main/java/kafka/streams/word/count/KafkaStreamsWordCountApplication.java index 9c96c31..1369b79 100644 --- a/kafka-streams-samples/kafka-streams-word-count/src/main/java/kafka/streams/word/count/KafkaStreamsWordCountApplication.java +++ b/kafka-streams-samples/kafka-streams-word-count/src/main/java/kafka/streams/word/count/KafkaStreamsWordCountApplication.java @@ -16,22 +16,23 @@ package kafka.streams.word.count; +import java.time.Duration; +import java.util.Arrays; +import java.util.Date; +import java.util.function.Function; + import org.apache.kafka.common.serialization.Serdes; import org.apache.kafka.common.utils.Bytes; import org.apache.kafka.streams.KeyValue; +import org.apache.kafka.streams.kstream.Grouped; import org.apache.kafka.streams.kstream.KStream; import org.apache.kafka.streams.kstream.Materialized; import org.apache.kafka.streams.kstream.Serialized; import org.apache.kafka.streams.kstream.TimeWindows; + import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.stream.annotation.EnableBinding; -import org.springframework.cloud.stream.annotation.StreamListener; -import org.springframework.cloud.stream.binder.kafka.streams.annotations.KafkaStreamsProcessor; -import org.springframework.messaging.handler.annotation.SendTo; - -import java.util.Arrays; -import java.util.Date; +import org.springframework.context.annotation.Bean; @SpringBootApplication public class KafkaStreamsWordCountApplication { @@ -40,22 +41,20 @@ public class KafkaStreamsWordCountApplication { SpringApplication.run(KafkaStreamsWordCountApplication.class, args); } - @EnableBinding(KafkaStreamsProcessor.class) public static class WordCountProcessorApplication { public static final String INPUT_TOPIC = "input"; public static final String OUTPUT_TOPIC = "output"; public static final int WINDOW_SIZE_MS = 30000; - @StreamListener(INPUT_TOPIC) - @SendTo(OUTPUT_TOPIC) - public KStream process(KStream input) { + @Bean + public Function, KStream> process() { - return input + return input -> input .flatMapValues(value -> Arrays.asList(value.toLowerCase().split("\\W+"))) .map((key, value) -> new KeyValue<>(value, value)) - .groupByKey(Serialized.with(Serdes.String(), Serdes.String())) - .windowedBy(TimeWindows.of(WINDOW_SIZE_MS)) + .groupByKey(Grouped.with(Serdes.String(), Serdes.String())) + .windowedBy(TimeWindows.of(Duration.ofMillis(WINDOW_SIZE_MS))) .count(Materialized.as("WordCounts-1")) .toStream() .map((key, value) -> new KeyValue<>(null, new WordCount(key.key(), value, new Date(key.window().start()), new Date(key.window().end())))); diff --git a/kafka-streams-samples/kafka-streams-word-count/src/main/java/kafka/streams/word/count/SampleRunner.java b/kafka-streams-samples/kafka-streams-word-count/src/main/java/kafka/streams/word/count/SampleRunner.java deleted file mode 100644 index a33bd91..0000000 --- a/kafka-streams-samples/kafka-streams-word-count/src/main/java/kafka/streams/word/count/SampleRunner.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package kafka.streams.word.count; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.cloud.stream.annotation.EnableBinding; -import org.springframework.cloud.stream.annotation.Input; -import org.springframework.cloud.stream.annotation.Output; -import org.springframework.cloud.stream.annotation.StreamListener; -import org.springframework.context.annotation.Bean; -import org.springframework.integration.annotation.InboundChannelAdapter; -import org.springframework.integration.annotation.Poller; -import org.springframework.integration.core.MessageSource; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.SubscribableChannel; -import org.springframework.messaging.support.GenericMessage; - -import java.util.Random; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * Provides a test source and sink to trigger the kafka streams processor - * and test the output respectively. - * - * @author Soby Chacko - */ -public class SampleRunner { - - //Following code is only used as a test harness. - - //Following source is used as test producer. - @EnableBinding(TestSource.class) - static class TestProducer { - - private AtomicBoolean semaphore = new AtomicBoolean(true); - - private String[] randomWords = new String[]{"foo", "bar", "foobar", "baz", "fox"}; - private Random random = new Random(); - - @Bean - @InboundChannelAdapter(channel = TestSource.OUTPUT, poller = @Poller(fixedDelay = "1000")) - public MessageSource sendTestData() { - return () -> { - int idx = random.nextInt(5); - return new GenericMessage<>(randomWords[idx]); - }; - } - } - - //Following sink is used as test consumer for the above processor. It logs the data received through the processor. - @EnableBinding(TestSink.class) - static class TestConsumer { - - private final Log logger = LogFactory.getLog(getClass()); - - @StreamListener(TestSink.INPUT) - public void receive(String data) { - logger.info("Data received..." + data); - } - } - - interface TestSink { - - String INPUT = "input1"; - - @Input(INPUT) - SubscribableChannel input1(); - - } - - interface TestSource { - - String OUTPUT = "output1"; - - @Output(TestSource.OUTPUT) - MessageChannel output(); - - } -} diff --git a/kafka-streams-samples/kafka-streams-word-count/src/main/resources/application.yml b/kafka-streams-samples/kafka-streams-word-count/src/main/resources/application.yml index 0f5f0a0..848296b 100644 --- a/kafka-streams-samples/kafka-streams-word-count/src/main/resources/application.yml +++ b/kafka-streams-samples/kafka-streams-word-count/src/main/resources/application.yml @@ -1,18 +1,28 @@ -spring.cloud.stream.kafka.streams.binder.configuration.commit.interval.ms: 1000 -spring.cloud.stream.kafka.streams: - binder.configuration: - default.key.serde: org.apache.kafka.common.serialization.Serdes$StringSerde - default.value.serde: org.apache.kafka.common.serialization.Serdes$StringSerde - bindings.input.consumer.application-id: basic-word-count -spring.cloud.stream.bindings.output: - destination: counts -spring.cloud.stream.bindings.input: - destination: words -#For testing -spring.cloud.stream.bindings.input1.destination: counts -spring.cloud.stream.bindings.output1.destination: words -spring.cloud.stream.bindings.input1.binder: kafka -spring.cloud.stream.bindings.output1.binder: kafka - - - +spring.cloud.stream: + bindings: + process-in-0: + destination: words + process-out-0: + destination: counts + kafka: + streams: + binder: + applicationId: hello-word-count-sample + configuration: + commit.interval.ms: 100 + default: + key.serde: org.apache.kafka.common.serialization.Serdes$StringSerde + value.serde: org.apache.kafka.common.serialization.Serdes$StringSerde +#Enable metrics +management: + endpoint: + health: + show-details: ALWAYS + endpoints: + web: + exposure: + include: metrics,health +#Enable logging to debug for spring kafka config +logging: + level: + org.springframework.kafka.config: debug diff --git a/kafka-streams-samples/kafka-streams-word-count/src/test/java/kafka/streams/word/count/KafkaStreamsWordCountApplicationTests.java b/kafka-streams-samples/kafka-streams-word-count/src/test/java/kafka/streams/word/count/KafkaStreamsWordCountApplicationTests.java index 75e6d6b..d4d4b4d 100644 --- a/kafka-streams-samples/kafka-streams-word-count/src/test/java/kafka/streams/word/count/KafkaStreamsWordCountApplicationTests.java +++ b/kafka-streams-samples/kafka-streams-word-count/src/test/java/kafka/streams/word/count/KafkaStreamsWordCountApplicationTests.java @@ -16,15 +16,20 @@ package kafka.streams.word.count; +import java.util.Map; + import org.apache.kafka.clients.consumer.Consumer; import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.kafka.clients.consumer.ConsumerRecords; import org.junit.AfterClass; +import org.junit.Before; import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Test; import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.kafka.config.StreamsBuilderFactoryBean; import org.springframework.kafka.core.DefaultKafkaConsumerFactory; import org.springframework.kafka.core.DefaultKafkaProducerFactory; import org.springframework.kafka.core.KafkaTemplate; @@ -33,21 +38,12 @@ import org.springframework.kafka.test.rule.EmbeddedKafkaRule; import org.springframework.kafka.test.utils.KafkaTestUtils; import org.springframework.test.context.junit4.SpringRunner; -import java.util.Map; - import static org.assertj.core.api.Assertions.assertThat; @RunWith(SpringRunner.class) @SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.NONE, - properties = {"server.port=0", - "spring.jmx.enabled=false", - "spring.cloud.stream.bindings.input.destination=words", - "spring.cloud.stream.bindings.output.destination=counts", - "spring.cloud.stream.kafka.streams.default.consumer.application-id=basic-word-count", - "spring.cloud.stream.kafka.streams.binder.configuration.commit.interval.ms=1000", - "spring.cloud.stream.kafka.streams.binder.configuration.default.key.serde=org.apache.kafka.common.serialization.Serdes$StringSerde", - "spring.cloud.stream.kafka.streams.binder.configuration.default.value.serde=org.apache.kafka.common.serialization.Serdes$StringSerde"}) + properties = {"server.port=0"}) public class KafkaStreamsWordCountApplicationTests { @ClassRule @@ -57,25 +53,32 @@ public class KafkaStreamsWordCountApplicationTests { private static Consumer consumer; + @Autowired + StreamsBuilderFactoryBean streamsBuilderFactoryBean; + + @Before + public void before() { + streamsBuilderFactoryBean.setCloseTimeout(0); + } + @BeforeClass - public static void setUp() throws Exception { + public static void setUp() { Map consumerProps = KafkaTestUtils.consumerProps("group", "false", embeddedKafka); consumerProps.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest"); DefaultKafkaConsumerFactory cf = new DefaultKafkaConsumerFactory<>(consumerProps); consumer = cf.createConsumer(); embeddedKafka.consumeFromAnEmbeddedTopic(consumer, "counts"); - //Since there are both binders present in this app, we resort to the spring kafka broker property. - System.setProperty("spring.kafka.bootstrap-servers", embeddedKafka.getBrokersAsString()); + System.setProperty("spring.cloud.stream.kafka.streams.binder.brokers", embeddedKafka.getBrokersAsString()); } @AfterClass public static void tearDown() { consumer.close(); - System.clearProperty("spring.kafka.bootstrap-servers"); + System.clearProperty("spring.cloud.stream.kafka.streams.binder.brokers"); } @Test - public void testKafkaStreamsWordCountProcessor() throws Exception { + public void testKafkaStreamsWordCountProcessor() { Map senderProps = KafkaTestUtils.producerProps(embeddedKafka); DefaultKafkaProducerFactory pf = new DefaultKafkaProducerFactory<>(senderProps); try { diff --git a/kafka-streams-samples/kafka-streams-word-count/src/test/java/kafka/streams/word/count/WordCountProcessorApplicationTests.java b/kafka-streams-samples/kafka-streams-word-count/src/test/java/kafka/streams/word/count/WordCountProcessorApplicationTests.java index 7bbbd28..86d5faa 100644 --- a/kafka-streams-samples/kafka-streams-word-count/src/test/java/kafka/streams/word/count/WordCountProcessorApplicationTests.java +++ b/kafka-streams-samples/kafka-streams-word-count/src/test/java/kafka/streams/word/count/WordCountProcessorApplicationTests.java @@ -34,6 +34,7 @@ import org.junit.Test; import org.springframework.kafka.support.serializer.JsonSerde; import java.util.*; +import java.util.function.Function; import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; @@ -75,8 +76,12 @@ public class WordCountProcessorApplicationTests { final StreamsBuilder builder = new StreamsBuilder(); KStream input = builder.stream(INPUT_TOPIC, Consumed.with(nullSerde, stringSerde)); KafkaStreamsWordCountApplication.WordCountProcessorApplication app = new KafkaStreamsWordCountApplication.WordCountProcessorApplication(); - KStream output = app.process(input); - output.to(OUTPUT_TOPIC, Produced.with(nullSerde, countSerde)); + final Function, KStream> process = app.process(); + + final KStream output = process.apply(input); + + output.to(OUTPUT_TOPIC, Produced.with(nullSerde, countSerde)); + testDriver = new TopologyTestDriver(builder.build(), getStreamsConfiguration()); }