├── kafka
├── build-docker.sh
├── build.sh
├── run-kafka.sh
├── Dockerfile
├── build-docker-kafka-multi-arch.sh
└── config
│ └── server.properties
├── deploy-artifacts.sh
├── .gitignore
├── docker-compose-registry.yml
├── eventuate-messaging-kafka-leadership
├── src
│ ├── test
│ │ ├── resources
│ │ │ ├── application.properties
│ │ │ └── logback.xml
│ │ └── java
│ │ │ └── io
│ │ │ └── eventuate
│ │ │ └── coordination
│ │ │ └── leadership
│ │ │ └── zookeeper
│ │ │ └── KafkaLeadershipTest.java
│ └── main
│ │ └── java
│ │ └── io
│ │ └── eventuate
│ │ └── coordination
│ │ └── leadership
│ │ └── zookeeper
│ │ ├── KafkaLeadershipController.java
│ │ └── KafkaLeaderSelector.java
└── build.gradle
├── gradle
├── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
└── gradlew.bat
├── set-multi-arch-image-env-vars.sh
├── eventuate-messaging-kafka-producer
├── build.gradle
└── src
│ └── main
│ └── java
│ └── io
│ └── eventuate
│ └── messaging
│ └── kafka
│ └── producer
│ ├── EventuateKafkaProducerConfigurationProperties.java
│ ├── EventuateKafkaPartitioner.java
│ └── EventuateKafkaProducer.java
├── eventuate-messaging-kafka-basic-consumer
├── src
│ ├── main
│ │ └── java
│ │ │ └── io
│ │ │ └── eventuate
│ │ │ └── messaging
│ │ │ └── kafka
│ │ │ └── basic
│ │ │ └── consumer
│ │ │ ├── MessageConsumerBacklog.java
│ │ │ ├── EventuateKafkaConsumerState.java
│ │ │ ├── ConsumerCallbacks.java
│ │ │ ├── KafkaMessageProcessorFailedException.java
│ │ │ ├── KafkaConsumerFactory.java
│ │ │ ├── BackPressureManagerState.java
│ │ │ ├── DefaultKafkaConsumerFactory.java
│ │ │ ├── EventuateKafkaConsumerMessageHandler.java
│ │ │ ├── BackPressureManagerStateAndActions.java
│ │ │ ├── BackPressureConfig.java
│ │ │ ├── ConsumerPropertiesFactory.java
│ │ │ ├── BackPressureManagerNormalState.java
│ │ │ ├── BackPressureManager.java
│ │ │ ├── BackPressureActions.java
│ │ │ ├── KafkaMessageConsumer.java
│ │ │ ├── EventuateKafkaConsumerConfigurationProperties.java
│ │ │ ├── BackPressureManagerPausedState.java
│ │ │ ├── TopicPartitionOffsets.java
│ │ │ ├── OffsetTracker.java
│ │ │ ├── DefaultKafkaMessageConsumer.java
│ │ │ ├── KafkaMessageProcessor.java
│ │ │ └── EventuateKafkaConsumer.java
│ └── test
│ │ └── java
│ │ └── io
│ │ └── eventuate
│ │ └── messaging
│ │ └── kafka
│ │ └── basic
│ │ └── consumer
│ │ ├── TopicPartitionOffsetsTest.java
│ │ ├── OffsetTrackerTest.java
│ │ └── BackPressureManagerTest.java
└── build.gradle
├── README.md
├── eventuate-messaging-kafka-spring-integration-test
├── src
│ └── test
│ │ ├── resources
│ │ └── application.properties
│ │ └── java
│ │ └── io
│ │ └── eventuate
│ │ └── messaging
│ │ └── kafka
│ │ └── spring
│ │ └── basic
│ │ └── consumer
│ │ ├── EventuateKafkaBasicConsumerSpringWithSwimlanePerTopicTest.java
│ │ └── EventuateKafkaBasicConsumerSpringTest.java
└── build.gradle
├── eventuate-messaging-kafka-consumer
├── src
│ ├── main
│ │ └── java
│ │ │ └── io
│ │ │ └── eventuate
│ │ │ └── messaging
│ │ │ └── kafka
│ │ │ └── consumer
│ │ │ ├── KafkaMessageHandler.java
│ │ │ ├── TopicPartitionToSwimlaneMapping.java
│ │ │ ├── ReactiveKafkaMessageHandler.java
│ │ │ ├── KafkaMessage.java
│ │ │ ├── KafkaSubscription.java
│ │ │ ├── OriginalTopicPartitionToSwimlaneMapping.java
│ │ │ ├── RawKafkaMessage.java
│ │ │ ├── SwimlaneDispatcherBacklog.java
│ │ │ ├── SwimlanePerTopicPartition.java
│ │ │ ├── MultipleSwimlanesPerTopicPartitionMapping.java
│ │ │ ├── SwimlaneBasedDispatcher.java
│ │ │ ├── SwimlaneDispatcher.java
│ │ │ └── MessageConsumerKafkaImpl.java
│ └── test
│ │ └── java
│ │ └── io
│ │ └── eventuate
│ │ └── messaging
│ │ └── kafka
│ │ └── consumer
│ │ ├── SwimlanePerTopicPartitionTest.java
│ │ ├── MultipleSwimlanesPerTopicPartitionMappingTest.java
│ │ └── SwimlaneDispatcherTest.java
└── build.gradle
├── eventuate-messaging-kafka-common
├── src
│ ├── main
│ │ ├── java
│ │ │ └── io
│ │ │ │ └── eventuate
│ │ │ │ └── messaging
│ │ │ │ └── kafka
│ │ │ │ └── common
│ │ │ │ ├── TopicCleaner.java
│ │ │ │ ├── AggregateTopicMapping.java
│ │ │ │ ├── EventuateKafkaMultiMessageHeader.java
│ │ │ │ ├── EventuateKafkaMultiMessagesHeader.java
│ │ │ │ ├── EventuateBinaryMessageEncoding.java
│ │ │ │ ├── EventuateKafkaConfigurationProperties.java
│ │ │ │ ├── EventuateKafkaMultiMessage.java
│ │ │ │ ├── EventuateKafkaMultiMessages.java
│ │ │ │ └── KeyValue.java
│ │ └── resources
│ │ │ └── sbe-schema.xml
│ └── test
│ │ └── java
│ │ └── io
│ │ └── eventuate
│ │ └── messaging
│ │ └── kafka
│ │ └── common
│ │ ├── sbe
│ │ └── SbeMessageSerdeTest.java
│ │ └── EventuateKafkaMultiMessageConverterTest.java
└── build.gradle
├── eventuate-messaging-kafka-spring-common
├── build.gradle
└── src
│ └── main
│ └── java
│ └── io
│ └── eventuate
│ └── messaging
│ └── kafka
│ └── spring
│ └── common
│ └── EventuateKafkaPropertiesConfiguration.java
├── eventuate-messaging-kafka-spring-producer
├── src
│ ├── test
│ │ ├── resources
│ │ │ └── application.properties
│ │ └── java
│ │ │ └── io
│ │ │ └── eventuate
│ │ │ └── messaging
│ │ │ └── kafka
│ │ │ └── spring
│ │ │ └── producer
│ │ │ └── EventuateKafkaProducerConfigurationSpringTest.java
│ └── main
│ │ └── java
│ │ └── io
│ │ └── eventuate
│ │ └── messaging
│ │ └── kafka
│ │ └── spring
│ │ └── producer
│ │ ├── EventuateKafkaProducerSpringConfigurationProperties.java
│ │ └── EventuateKafkaProducerSpringConfigurationPropertiesConfiguration.java
└── build.gradle
├── eventuate-messaging-kafka-micronaut-producer
├── src
│ ├── test
│ │ ├── resources
│ │ │ └── application.properties
│ │ └── java
│ │ │ └── io
│ │ │ └── eventuate
│ │ │ └── messaging
│ │ │ └── kafka
│ │ │ └── micronaut
│ │ │ └── producer
│ │ │ └── EventuateKafkaProducerConfigurationTest.java
│ └── main
│ │ └── java
│ │ └── io
│ │ └── eventuate
│ │ └── messaging
│ │ └── kafka
│ │ └── micronaut
│ │ └── producer
│ │ ├── EventuateKafkaProducerMicronautConfigurationProperties.java
│ │ └── EventuateKafkaProducerConfigurationPropertiesFactory.java
└── build.gradle
├── eventuate-messaging-kafka-testcontainers
├── src
│ ├── test
│ │ ├── resources
│ │ │ └── logback.xml
│ │ └── java
│ │ │ └── io
│ │ │ └── eventuate
│ │ │ └── messaging
│ │ │ └── kafka
│ │ │ └── testcontainers
│ │ │ ├── EventuateKafkaClusterTest.java
│ │ │ ├── EventuateKafkaNativeClusterTest.java
│ │ │ └── EventuateKafkaContainerTest.java
│ └── main
│ │ └── java
│ │ └── io
│ │ └── eventuate
│ │ └── messaging
│ │ └── kafka
│ │ └── testcontainers
│ │ ├── EventuateKafkaNativeCluster.java
│ │ ├── EventuateKafkaCluster.java
│ │ ├── EventuateKafkaNativeContainer.java
│ │ └── EventuateKafkaContainer.java
└── build.gradle
├── .circleci
├── save-containers-and-tests.sh
└── config.yml
├── eventuate-messaging-kafka-spring-consumer
├── build.gradle
└── src
│ └── main
│ └── java
│ └── io
│ └── eventuate
│ └── messaging
│ └── kafka
│ └── spring
│ └── consumer
│ ├── KafkaConsumerFactoryConfiguration.java
│ └── MessageConsumerKafkaConfiguration.java
├── eventuate-messaging-kafka-micronaut-basic-consumer
├── src
│ ├── test
│ │ ├── resources
│ │ │ └── application.properties
│ │ └── java
│ │ │ └── io
│ │ │ └── eventuate
│ │ │ └── messaging
│ │ │ └── kafka
│ │ │ └── micronaut
│ │ │ └── basic
│ │ │ └── consumer
│ │ │ └── EventuateKafkaConsumerConfigurationMicronautTest.java
│ └── main
│ │ └── java
│ │ └── io
│ │ └── eventuate
│ │ └── messaging
│ │ └── kafka
│ │ └── micronaut
│ │ └── basic
│ │ └── consumer
│ │ ├── EventuateKafkaConsumerConfigurationPropertiesFactory.java
│ │ └── EventuateKafkaConsumerMicronautConfigurationProperties.java
└── build.gradle
├── eventuate-messaging-kafka-micronaut-integration-test
├── src
│ └── test
│ │ ├── resources
│ │ └── application.properties
│ │ └── java
│ │ └── io
│ │ └── eventuate
│ │ └── messaging
│ │ └── kafka
│ │ └── basic
│ │ └── consumer
│ │ ├── EventuateKafkaProducerConsumerFactory.java
│ │ └── EventuateKafkaBasicConsumerMicronautTest.java
└── build.gradle
├── eventuate-messaging-kafka-spring-basic-consumer
├── src
│ ├── test
│ │ ├── resources
│ │ │ └── application.properties
│ │ └── java
│ │ │ └── io
│ │ │ └── eventuate
│ │ │ └── messaging
│ │ │ └── kafka
│ │ │ └── spring
│ │ │ └── basic
│ │ │ └── consumer
│ │ │ └── EventuateKafkaConsumerConfigurationSpringTest.java
│ └── main
│ │ └── java
│ │ └── io
│ │ └── eventuate
│ │ └── messaging
│ │ └── kafka
│ │ └── spring
│ │ └── basic
│ │ └── consumer
│ │ ├── EventuateKafkaConsumerSpringConfigurationProperties.java
│ │ └── EventuateKafkaConsumerSpringConfigurationPropertiesConfiguration.java
└── build.gradle
├── .github
└── workflows
│ ├── print-container-logs.sh
│ └── build-and-test.yaml
├── LICENSE
├── eventuate-messaging-kafka-micronaut-consumer
├── src
│ └── main
│ │ └── java
│ │ └── io
│ │ └── eventuate
│ │ └── messaging
│ │ └── kafka
│ │ └── micronaut
│ │ └── consumer
│ │ ├── KafkaConsumerFactoryFactory.java
│ │ └── MessageConsumerKafkaFactory.java
└── build.gradle
├── gradle.properties
├── eventuate-messaging-kafka-micronaut-common
├── build.gradle
└── src
│ └── main
│ └── java
│ └── io
│ └── eventuate
│ └── messaging
│ └── kafka
│ └── micronaut
│ └── common
│ └── EventuateKafkaPropertiesFactory.java
├── eventuate-messaging-kafka-integration-test
├── src
│ └── main
│ │ ├── resources
│ │ └── application.properties
│ │ └── java
│ │ └── io
│ │ └── eventuate
│ │ └── messaging
│ │ └── kafka
│ │ └── basic
│ │ └── consumer
│ │ └── AbstractEventuateKafkaBasicConsumerTest.java
└── build.gradle
├── docker-compose.yml
├── deploy-multi-arch.sh
├── settings.gradle
├── gradlew.bat
└── gradlew
/kafka/build-docker.sh:
--------------------------------------------------------------------------------
1 | docker build -t test-eventuate-kafka .
--------------------------------------------------------------------------------
/deploy-artifacts.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash -e
2 |
3 | ./gradlew publishEventuateArtifacts
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | build/
3 | *.idea/
4 | *.iml
5 | *.log
6 | out
7 | .classpath
8 | .project
9 | .settings
10 | bin
11 |
--------------------------------------------------------------------------------
/docker-compose-registry.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | registry:
4 | image: registry:2
5 | ports:
6 | - "5002:5000"
7 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-leadership/src/test/resources/application.properties:
--------------------------------------------------------------------------------
1 | eventuatelocal.zookeeper.connection.string=localhost:2181
2 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eventuate-foundation/eventuate-messaging-kafka/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/set-multi-arch-image-env-vars.sh:
--------------------------------------------------------------------------------
1 | export MULTI_ARCH_TAG=test-build-${CIRCLE_SHA1?}
2 | export BUILDX_PUSH_OPTIONS=--push
3 |
4 | export KAFKA_MULTI_ARCH_IMAGE=eventuateio/eventuate-kafka:$MULTI_ARCH_TAG
5 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-producer/build.gradle:
--------------------------------------------------------------------------------
1 |
2 | dependencies {
3 | implementation project (":eventuate-messaging-kafka-common")
4 | }
5 | tasks.withType(Test).configureEach {
6 | useJUnitPlatform()
7 | }
8 |
--------------------------------------------------------------------------------
/kafka/build.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash -e
2 |
3 | #docker build -t test-eventuateio-local-kafka .
4 |
5 | docker buildx build --platform linux/amd64,linux/arm64 -t 182030608133.dkr.ecr.us-west-1.amazonaws.com/test-repo/eventuate-kafka --push .
6 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-basic-consumer/src/main/java/io/eventuate/messaging/kafka/basic/consumer/MessageConsumerBacklog.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.basic.consumer;
2 |
3 | public interface MessageConsumerBacklog {
4 | int size();
5 | }
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # An Eventuate project
2 |
3 |
4 |
5 | This project is part of [Eventuate](http://eventuate.io), which is a microservices collaboration platform.
6 |
7 |
8 |
9 | # eventuate-messaging-kafka
10 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-spring-integration-test/src/test/resources/application.properties:
--------------------------------------------------------------------------------
1 | eventuatelocal.kafka.bootstrap.servers=${DOCKER_HOST_IP:localhost}:9092
2 | logging.level.io.eventuate.messaging.kafka.consumer=TRACE
3 | logging.level.io.eventuate.messaging.kafka.basic.consumer=TRACE
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-consumer/src/main/java/io/eventuate/messaging/kafka/consumer/KafkaMessageHandler.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.consumer;
2 |
3 | import java.util.function.Consumer;
4 |
5 | public interface KafkaMessageHandler extends Consumer {
6 |
7 | }
8 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-common/src/main/java/io/eventuate/messaging/kafka/common/TopicCleaner.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.common;
2 |
3 | public class TopicCleaner {
4 |
5 | public static String clean(String topic) {
6 | return topic.replace("$", "_DLR_");
7 | }
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-spring-common/build.gradle:
--------------------------------------------------------------------------------
1 |
2 | dependencies {
3 | implementation project (":eventuate-messaging-kafka-common")
4 |
5 | api "org.springframework.boot:spring-boot-starter:$springBootVersion"
6 | }
7 | tasks.withType(Test).configureEach {
8 | useJUnitPlatform()
9 | }
10 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-basic-consumer/src/main/java/io/eventuate/messaging/kafka/basic/consumer/EventuateKafkaConsumerState.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.basic.consumer;
2 |
3 | public enum EventuateKafkaConsumerState {
4 | MESSAGE_HANDLING_FAILED, STARTED, FAILED_TO_START, STOPPED, FAILED, CREATED
5 | }
6 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-spring-producer/src/test/resources/application.properties:
--------------------------------------------------------------------------------
1 | eventuate.local.kafka.producer.properties.buffer.memory=1000000
2 | eventuate.local.kafka.producer.properties.value.serializer=org.apache.kafka.common.serialization.ByteArraySerializer
3 | not.eventuate.local.kafka.producer.properties.property=N/A
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-basic-consumer/src/main/java/io/eventuate/messaging/kafka/basic/consumer/ConsumerCallbacks.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.basic.consumer;
2 |
3 | public interface ConsumerCallbacks {
4 | void onTryCommitCallback();
5 | void onCommitedCallback();
6 | void onCommitFailedCallback();
7 | }
8 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-micronaut-producer/src/test/resources/application.properties:
--------------------------------------------------------------------------------
1 | eventuate.local.kafka.producer.properties.buffer-memory=1000000
2 | eventuate.local.kafka.producer.properties.value-serializer=org.apache.kafka.common.serialization.ByteArraySerializer
3 | not.eventuate.local.kafka.producer.properties.property=N/A
4 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-common/src/main/java/io/eventuate/messaging/kafka/common/AggregateTopicMapping.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.common;
2 |
3 | public class AggregateTopicMapping {
4 |
5 | public static String aggregateTypeToTopic(String aggregateType) {
6 | return TopicCleaner.clean(aggregateType);
7 | }
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-common/src/main/java/io/eventuate/messaging/kafka/common/EventuateKafkaMultiMessageHeader.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.common;
2 |
3 | public class EventuateKafkaMultiMessageHeader extends KeyValue {
4 |
5 | public EventuateKafkaMultiMessageHeader(String key, String value) {
6 | super(key, value);
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-common/src/main/java/io/eventuate/messaging/kafka/common/EventuateKafkaMultiMessagesHeader.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.common;
2 |
3 | public class EventuateKafkaMultiMessagesHeader extends KeyValue {
4 |
5 | public EventuateKafkaMultiMessagesHeader(String key, String value) {
6 | super(key, value);
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/kafka/run-kafka.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash -e
2 |
3 | set -e
4 |
5 | EFFECTIVE_KAFKA_CONFIG_DIR=./config
6 |
7 | export KAFKA_LOG_DIRS=${KAFKA_LOG_DIRS:-/tmp/kafka-logs}
8 |
9 | envsubst < /usr/local/kafka-config/server.properties > $EFFECTIVE_KAFKA_CONFIG_DIR/server.properties
10 |
11 | exec bin/kafka-server-start.sh $EFFECTIVE_KAFKA_CONFIG_DIR/server.properties
12 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-consumer/src/main/java/io/eventuate/messaging/kafka/consumer/TopicPartitionToSwimlaneMapping.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.consumer;
2 |
3 | import org.apache.kafka.common.TopicPartition;
4 |
5 | public interface TopicPartitionToSwimlaneMapping {
6 | Integer toSwimlane(TopicPartition topicPartition, String messageKey);
7 | }
8 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-basic-consumer/src/main/java/io/eventuate/messaging/kafka/basic/consumer/KafkaMessageProcessorFailedException.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.basic.consumer;
2 |
3 | public class KafkaMessageProcessorFailedException extends RuntimeException {
4 | public KafkaMessageProcessorFailedException(Throwable t) {
5 | super(t);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-basic-consumer/src/main/java/io/eventuate/messaging/kafka/basic/consumer/KafkaConsumerFactory.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.basic.consumer;
2 |
3 | import java.util.Properties;
4 |
5 | public interface KafkaConsumerFactory {
6 |
7 | KafkaMessageConsumer makeConsumer(String subscriptionId, Properties consumerProperties);
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-consumer/src/main/java/io/eventuate/messaging/kafka/consumer/ReactiveKafkaMessageHandler.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.consumer;
2 |
3 | import java.util.concurrent.CompletableFuture;
4 | import java.util.function.Function;
5 |
6 | public interface ReactiveKafkaMessageHandler extends Function> {
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-consumer/src/main/java/io/eventuate/messaging/kafka/consumer/KafkaMessage.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.consumer;
2 |
3 | public class KafkaMessage {
4 | private String payload;
5 |
6 | public KafkaMessage(String payload) {
7 | this.payload = payload;
8 | }
9 |
10 | public String getPayload() {
11 | return payload;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionSha256Sum=bd71102213493060956ec229d946beee57158dbd89d0e62b91bca0fa2c5f3531
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
5 | networkTimeout=10000
6 | validateDistributionUrl=true
7 | zipStoreBase=GRADLE_USER_HOME
8 | zipStorePath=wrapper/dists
9 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-leadership/src/test/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-testcontainers/src/test/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-consumer/src/main/java/io/eventuate/messaging/kafka/consumer/KafkaSubscription.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.consumer;
2 |
3 | public class KafkaSubscription {
4 | private Runnable closingCallback;
5 |
6 | public KafkaSubscription(Runnable closingCallback) {
7 | this.closingCallback = closingCallback;
8 | }
9 |
10 | public void close() {
11 | closingCallback.run();
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/.circleci/save-containers-and-tests.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash -e
2 |
3 | mkdir -p ~/junit /home/circleci/container-logs
4 | docker ps -a > /home/circleci/container-logs/containers.txt
5 | find . -type f -regex ".*/build/test-results/.*xml" -exec cp {} ~/junit/ \;
6 | sudo bash -c 'find /var/lib/docker/containers -name "*-json.log" -exec cp {} /home/circleci/container-logs \;'
7 | sudo bash -c 'find /home/circleci/container-logs -type f -exec chown circleci {} \;'
8 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-spring-consumer/build.gradle:
--------------------------------------------------------------------------------
1 |
2 | dependencies {
3 | api project (":eventuate-messaging-kafka-consumer")
4 | api project (":eventuate-messaging-kafka-spring-basic-consumer")
5 | api project (":eventuate-messaging-kafka-common")
6 |
7 | implementation "org.springframework.boot:spring-boot-starter:$springBootVersion"
8 | }
9 | tasks.withType(Test).configureEach {
10 | useJUnitPlatform()
11 | }
12 |
13 |
14 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-basic-consumer/src/main/java/io/eventuate/messaging/kafka/basic/consumer/BackPressureManagerState.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.basic.consumer;
2 |
3 | import org.apache.kafka.common.TopicPartition;
4 |
5 | import java.util.Set;
6 |
7 | public interface BackPressureManagerState {
8 | BackPressureManagerStateAndActions update(Set allTopicPartitions, int backlog, BackPressureConfig backPressureConfig);
9 | }
10 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-spring-producer/build.gradle:
--------------------------------------------------------------------------------
1 |
2 | dependencies {
3 | api project (":eventuate-messaging-kafka-producer")
4 | api project (':eventuate-messaging-kafka-spring-common')
5 |
6 | implementation "org.springframework.boot:spring-boot-starter:$springBootVersion"
7 |
8 | testImplementation "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
9 | }
10 | tasks.withType(Test).configureEach {
11 | useJUnitPlatform()
12 | }
13 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-micronaut-basic-consumer/src/test/resources/application.properties:
--------------------------------------------------------------------------------
1 | eventuate.local.kafka.consumer.properties.session-timeout-ms=10000
2 | eventuate.local.kafka.consumer.properties.key-serializer=org.apache.kafka.common.serialization.StringSerializer
3 | not.eventuate.local.kafka.consumer.properties.property=N/A
4 | eventuate.local.kafka.consumer.backPressure.low=5
5 | eventuate.local.kafka.consumer.backPressure.high=100
6 | eventuate.local.kafka.consumer.pollTimeout=200
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-basic-consumer/build.gradle:
--------------------------------------------------------------------------------
1 |
2 | dependencies {
3 | implementation 'org.apache.commons:commons-lang3:3.18.0'
4 | implementation "org.junit.jupiter:junit-jupiter:5.13.4"
5 | implementation project (':eventuate-messaging-kafka-common')
6 | implementation "org.slf4j:slf4j-api:$slf4jVersion"
7 |
8 | testImplementation "org.assertj:assertj-core:$assertjVersion"
9 |
10 | }
11 |
12 | test {
13 | forkEvery 1
14 | useJUnitPlatform()
15 | }
16 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-consumer/src/main/java/io/eventuate/messaging/kafka/consumer/OriginalTopicPartitionToSwimlaneMapping.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.consumer;
2 |
3 | import org.apache.kafka.common.TopicPartition;
4 |
5 | public class OriginalTopicPartitionToSwimlaneMapping implements TopicPartitionToSwimlaneMapping {
6 | @Override
7 | public Integer toSwimlane(TopicPartition topicPartition, String messageKey) {
8 | return topicPartition.partition();
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-basic-consumer/src/main/java/io/eventuate/messaging/kafka/basic/consumer/DefaultKafkaConsumerFactory.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.basic.consumer;
2 |
3 | import java.util.Properties;
4 |
5 | public class DefaultKafkaConsumerFactory implements KafkaConsumerFactory {
6 |
7 | @Override
8 | public KafkaMessageConsumer makeConsumer(String subscriptionId, Properties consumerProperties) {
9 | return DefaultKafkaMessageConsumer.create(consumerProperties);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-common/src/main/java/io/eventuate/messaging/kafka/common/EventuateBinaryMessageEncoding.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.common;
2 |
3 | import java.nio.charset.Charset;
4 |
5 | public class EventuateBinaryMessageEncoding {
6 | public static String bytesToString(byte[] bytes) {
7 | return new String(bytes, Charset.forName("UTF-8"));
8 | }
9 |
10 | public static byte[] stringToBytes(String string) {
11 | return string.getBytes(Charset.forName("UTF-8"));
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-basic-consumer/src/main/java/io/eventuate/messaging/kafka/basic/consumer/EventuateKafkaConsumerMessageHandler.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.basic.consumer;
2 |
3 | import org.apache.kafka.clients.consumer.ConsumerRecord;
4 |
5 | import java.util.function.BiConsumer;
6 | import java.util.function.BiFunction;
7 |
8 | public interface EventuateKafkaConsumerMessageHandler
9 | extends BiFunction, BiConsumer, MessageConsumerBacklog> {
10 | }
11 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-micronaut-integration-test/src/test/resources/application.properties:
--------------------------------------------------------------------------------
1 | eventuate.local.kafka.consumer.properties.session-timeout-ms=30000
2 | eventuate.local.kafka.consumer.properties.key-serializer=org.apache.kafka.common.serialization.StringSerializer
3 |
4 | eventuate.local.kafka.producer.properties.buffer-memory=1000000
5 | eventuate.local.kafka.producer.properties.value.serializer=org.apache.kafka.common.serialization.ByteArraySerializer
6 |
7 | eventuatelocal.kafka.bootstrap.servers=${DOCKER_HOST_IP:localhost}:9092
8 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-consumer/src/main/java/io/eventuate/messaging/kafka/consumer/RawKafkaMessage.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.consumer;
2 |
3 | public class RawKafkaMessage {
4 | private final String messageKey;
5 | private final byte[] payload;
6 |
7 | public RawKafkaMessage(String messageKey, byte[] payload) {
8 | this.messageKey = messageKey;
9 | this.payload = payload;
10 | }
11 |
12 | public String getMessageKey() {
13 | return messageKey;
14 | }
15 |
16 | public byte[] getPayload() {
17 | return payload;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-spring-integration-test/build.gradle:
--------------------------------------------------------------------------------
1 |
2 | dependencies {
3 | implementation project (':eventuate-messaging-kafka-integration-test')
4 | implementation project (':eventuate-messaging-kafka-spring-basic-consumer')
5 | implementation project (':eventuate-messaging-kafka-spring-producer')
6 | implementation project (':eventuate-messaging-kafka-spring-common')
7 |
8 | testImplementation "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
9 | }
10 | tasks.withType(Test).configureEach {
11 | useJUnitPlatform()
12 | }
13 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-common/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: SbeToolPlugin
2 |
3 | dependencies {
4 | implementation 'org.apache.commons:commons-lang3:3.18.0'
5 | implementation "org.hamcrest:hamcrest:2.2"
6 | implementation "org.junit.jupiter:junit-jupiter:5.13.4"
7 | api "org.apache.kafka:kafka-clients:$kafkaClientVersion"
8 |
9 | implementation 'uk.co.real-logic:sbe-all:1.32.1'
10 |
11 | implementation "org.slf4j:slf4j-api:$slf4jVersion"
12 |
13 | testImplementation "org.hamcrest:hamcrest-library:1.3"
14 | }
15 | tasks.withType(Test).configureEach {
16 | useJUnitPlatform()
17 | }
18 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-spring-basic-consumer/src/test/resources/application.properties:
--------------------------------------------------------------------------------
1 | eventuate.local.kafka.consumer.properties.session.timeout.ms=10000
2 | eventuate.local.kafka.consumer.properties.key.serializer=org.apache.kafka.common.serialization.StringSerializer
3 | not.eventuate.local.kafka.consumer.properties.property=N/A
4 | eventuate.local.kafka.consumer.backPressure.low=5
5 | eventuate.local.kafka.consumer.backPressure.high=100
6 | eventuate.local.kafka.consumer.pollTimeout=200
7 |
8 | logging.level.io.eventuate.tram=DEBUG
9 |
10 | eventuatelocal.kafka.bootstrap.servers=${DOCKER_HOST_IP:localhost}:9092
11 |
--------------------------------------------------------------------------------
/.github/workflows/print-container-logs.sh:
--------------------------------------------------------------------------------
1 | π#! /bin/bash -e
2 |
3 | CONTAINER_IDS=$(docker ps -a -q)
4 |
5 | for id in $CONTAINER_IDS ; do
6 | echo "\n--------------------"
7 | echo "logs of:\n"
8 | (docker ps -a -f "id=$id" || echo container gone: $id)
9 | echo "\n"
10 | (docker logs $id || echo container gone: $id)
11 | echo "--------------------\n"
12 | done
13 |
14 | mkdir -p ~/container-logs
15 |
16 | docker ps -a > ~/container-logs/containers.txt
17 |
18 | for name in $(docker ps -a --format "{{.Names}}") ; do
19 | (docker logs $name || echo container gone: $name) > ~/container-logs/${name}.log
20 | done
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2020 Eventuate, Inc. All rights reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-consumer/src/main/java/io/eventuate/messaging/kafka/consumer/SwimlaneDispatcherBacklog.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.consumer;
2 |
3 | import io.eventuate.messaging.kafka.basic.consumer.MessageConsumerBacklog;
4 |
5 | import java.util.concurrent.LinkedBlockingQueue;
6 |
7 | public class SwimlaneDispatcherBacklog implements MessageConsumerBacklog {
8 | private final LinkedBlockingQueue> queue;
9 |
10 | public SwimlaneDispatcherBacklog(LinkedBlockingQueue> queue) {
11 | this.queue = queue;
12 | }
13 |
14 | @Override
15 | public int size() {
16 | return queue.size();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/kafka/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM amazoncorretto:8u322-al2
2 | ENV KAFKA_VERSION=3.2.3
3 |
4 | RUN yum install -y wget tar gzip nc gettext && \
5 | (wget -q -O - https://archive.apache.org/dist/kafka/${KAFKA_VERSION}/kafka_2.13-${KAFKA_VERSION}.tgz | tar -xzf - -C /usr/local) && \
6 | yum clean all && \
7 | rm -rf /var/cache/yum
8 | WORKDIR /usr/local/kafka_2.13-${KAFKA_VERSION}
9 | EXPOSE 9092
10 | COPY ./config/server.properties /usr/local/kafka-config/server.properties
11 | COPY run-kafka.sh .
12 | CMD ./run-kafka.sh
13 | HEALTHCHECK --interval=1s --retries=30 CMD (nc -z $(echo $KAFKA_LISTENERS | sed -e 's?.*//??' -e 's/:/ /' )) || exit 1
14 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-consumer/build.gradle:
--------------------------------------------------------------------------------
1 |
2 | dependencies {
3 | api project (":eventuate-messaging-kafka-basic-consumer")
4 | implementation project (":eventuate-messaging-kafka-common")
5 | api "io.eventuate.common.messaging:eventuate-messaging-partition-management:$eventuateCommonMessagingVersion"
6 | implementation "org.apache.kafka:kafka-clients:$kafkaClientVersion"
7 |
8 | testImplementation "io.eventuate.util:eventuate-util-test:$eventuateUtilVersion"
9 | testImplementation "org.assertj:assertj-core:$assertjVersion"
10 | }
11 | tasks.withType(Test).configureEach {
12 | useJUnitPlatform()
13 | }
14 |
15 |
16 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-basic-consumer/src/main/java/io/eventuate/messaging/kafka/basic/consumer/BackPressureManagerStateAndActions.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.basic.consumer;
2 |
3 | public class BackPressureManagerStateAndActions {
4 |
5 | final BackPressureActions actions;
6 | final BackPressureManagerState state;
7 |
8 | public BackPressureManagerStateAndActions(BackPressureActions actions, BackPressureManagerState state) {
9 | this.actions = actions;
10 | this.state = state;
11 | }
12 |
13 | public BackPressureManagerStateAndActions(BackPressureManagerState state) {
14 | this(BackPressureActions.NONE, state);
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-consumer/src/main/java/io/eventuate/messaging/kafka/consumer/SwimlanePerTopicPartition.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.consumer;
2 |
3 | import org.apache.kafka.common.TopicPartition;
4 |
5 | import java.util.concurrent.ConcurrentHashMap;
6 |
7 | public class SwimlanePerTopicPartition implements TopicPartitionToSwimlaneMapping {
8 |
9 | private final ConcurrentHashMap mapping = new ConcurrentHashMap<>();
10 |
11 | @Override
12 | public Integer toSwimlane(TopicPartition topicPartition, String messageKey) {
13 | return mapping.computeIfAbsent(topicPartition, tp -> mapping.size());
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-spring-basic-consumer/build.gradle:
--------------------------------------------------------------------------------
1 |
2 | dependencies {
3 | api project (':eventuate-messaging-kafka-basic-consumer')
4 | api project (':eventuate-messaging-kafka-spring-common')
5 | implementation "org.springframework.boot:spring-boot-starter:$springBootVersion"
6 |
7 | testImplementation project (':eventuate-messaging-kafka-spring-producer')
8 | testImplementation project (':eventuate-messaging-kafka-common')
9 |
10 | testImplementation "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
11 | testImplementation "io.eventuate.util:eventuate-util-test:$eventuateUtilVersion"
12 | }
13 | tasks.withType(Test).configureEach {
14 | useJUnitPlatform()
15 | }
16 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-basic-consumer/src/main/java/io/eventuate/messaging/kafka/basic/consumer/BackPressureConfig.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.basic.consumer;
2 |
3 | public class BackPressureConfig {
4 |
5 | private int low = 0;
6 | private int high = Integer.MAX_VALUE;
7 |
8 | public BackPressureConfig() {
9 | }
10 |
11 | public BackPressureConfig(int low, int high) {
12 | this.low = low;
13 | this.high = high;
14 | }
15 |
16 | public int getLow() {
17 | return low;
18 | }
19 |
20 | public void setLow(int low) {
21 | this.low = low;
22 | }
23 |
24 | public int getHigh() {
25 | return high;
26 | }
27 |
28 | public void setHigh(int high) {
29 | this.high = high;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-micronaut-consumer/src/main/java/io/eventuate/messaging/kafka/micronaut/consumer/KafkaConsumerFactoryFactory.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.micronaut.consumer;
2 |
3 | import javax.inject.Singleton;
4 |
5 | import io.eventuate.messaging.kafka.basic.consumer.DefaultKafkaConsumerFactory;
6 | import io.eventuate.messaging.kafka.basic.consumer.KafkaConsumerFactory;
7 | import io.micronaut.context.annotation.Factory;
8 | import io.micronaut.context.annotation.Requires;
9 |
10 | @Factory
11 | public class KafkaConsumerFactoryFactory {
12 | @Singleton
13 | @Requires(missingBeans = KafkaConsumerFactory.class)
14 | public KafkaConsumerFactory kafkaConsumerFactory() {
15 | return new DefaultKafkaConsumerFactory();
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-common/src/main/java/io/eventuate/messaging/kafka/common/EventuateKafkaConfigurationProperties.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.common;
2 |
3 | public class EventuateKafkaConfigurationProperties {
4 |
5 | private String bootstrapServers;
6 | private long connectionValidationTimeout;
7 |
8 | public EventuateKafkaConfigurationProperties(String bootstrapServers, long connectionValidationTimeout) {
9 | this.bootstrapServers = bootstrapServers;
10 | this.connectionValidationTimeout = connectionValidationTimeout;
11 | }
12 |
13 | public String getBootstrapServers() {
14 | return bootstrapServers;
15 | }
16 |
17 | public long getConnectionValidationTimeout() {
18 | return connectionValidationTimeout;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 |
2 | deployUrl=file:///Users/cer/.m2/testdeploy
3 | dockerComposePluginVersion=0.17.7
4 |
5 | eventuateMavenRepoUrl=file:///Users/cer/.m2/testdeploy,https://snapshots.repositories.eventuate.io/repository
6 |
7 | kafkaClientVersion=3.1.1
8 |
9 | springBootVersion=3.4.7
10 | slf4jVersion=1.7.36
11 | micronautVersion=2.4.1
12 |
13 | eventuateUtilVersion=0.19.0.BUILD-SNAPSHOT
14 | eventuateCommonMessagingVersion=0.17.0.BUILD-SNAPSHOT
15 |
16 | eventuatePluginsGradleVersion=0.14.0.BUILD-SNAPSHOT
17 | junitVersion=5.12.2
18 | junitPlatformLauncherVersion=1.12.2
19 | eventuateCommonImageVersion=0.21.0.BUILD-SNAPSHOT
20 | eventuateCommonVersion=0.21.0.BUILD-SNAPSHOT
21 | assertjVersion=3.26.3
22 | testContainersVersion=1.20.1
23 | slf4jApiVersion=2.0.17
24 |
25 | version=0.21.0-SNAPSHOT
26 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-spring-consumer/src/main/java/io/eventuate/messaging/kafka/spring/consumer/KafkaConsumerFactoryConfiguration.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.spring.consumer;
2 |
3 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
4 | import org.springframework.context.annotation.Bean;
5 | import org.springframework.context.annotation.Configuration;
6 | import io.eventuate.messaging.kafka.basic.consumer.DefaultKafkaConsumerFactory;
7 | import io.eventuate.messaging.kafka.basic.consumer.KafkaConsumerFactory;
8 |
9 | @Configuration
10 | public class KafkaConsumerFactoryConfiguration {
11 | @Bean
12 | @ConditionalOnMissingBean
13 | public KafkaConsumerFactory kafkaConsumerFactory() {
14 | return new DefaultKafkaConsumerFactory();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-micronaut-common/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "io.spring.dependency-management" version "1.1.7"
3 | }
4 |
5 |
6 |
7 |
8 | dependencyManagement {
9 | imports {
10 | mavenBom "io.micronaut:micronaut-bom:$micronautVersion"
11 | }
12 | }
13 |
14 | dependencies {
15 | implementation project (":eventuate-messaging-kafka-common")
16 |
17 | annotationProcessor "io.micronaut:micronaut-inject-java"
18 | annotationProcessor "io.micronaut:micronaut-validation"
19 | annotationProcessor "io.micronaut.configuration:micronaut-openapi"
20 | implementation "io.micronaut:micronaut-inject"
21 | implementation "io.micronaut:micronaut-validation"
22 | implementation "io.micronaut:micronaut-runtime"
23 | }
24 | tasks.withType(Test).configureEach {
25 | useJUnitPlatform()
26 | }
27 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-micronaut-producer/src/main/java/io/eventuate/messaging/kafka/micronaut/producer/EventuateKafkaProducerMicronautConfigurationProperties.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.micronaut.producer;
2 |
3 | import io.micronaut.context.annotation.ConfigurationProperties;
4 |
5 | import java.util.HashMap;
6 | import java.util.Map;
7 | import java.util.stream.Collectors;
8 |
9 | @ConfigurationProperties("eventuate.local.kafka.producer")
10 | public class EventuateKafkaProducerMicronautConfigurationProperties {
11 | Map properties = new HashMap<>();
12 |
13 | public Map getProperties() {
14 | return properties
15 | .entrySet()
16 | .stream()
17 | .collect(Collectors.toMap(o -> o.getKey().replace("-", "."), Map.Entry::getValue));
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-integration-test/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | eventuate.local.kafka.consumer.properties.session.timeout.ms=30000
2 | eventuate.local.kafka.consumer.properties.key.serializer=org.apache.kafka.common.serialization.StringSerializer
3 |
4 | eventuate.local.kafka.producer.properties.buffer.memory=1000000
5 | eventuate.local.kafka.producer.properties.value.serializer=org.apache.kafka.common.serialization.ByteArraySerializer
6 |
7 | eventuatelocal.kafka.bootstrap.servers=${DOCKER_HOST_IP:localhost}:9092
8 | eventuate.local.kafka.consumer.properties.session.timeout.ms=10000
9 | logging.level.io.eventuate.local.java.kafka.consumer=TRACE
10 | logging.level.io.io.eventuate.messaging.kafka.basic.consumer=INFO
11 |
12 | eventuate.local.kafka.consumer.backPressure.low=5
13 | eventuate.local.kafka.consumer.backPressure.high=100
14 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-testcontainers/src/main/java/io/eventuate/messaging/kafka/testcontainers/EventuateKafkaNativeCluster.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.testcontainers;
2 |
3 | import io.eventuate.common.testcontainers.ReusableNetworkFactory;
4 | import org.testcontainers.containers.Network;
5 |
6 | public class EventuateKafkaNativeCluster {
7 |
8 | public final Network network;
9 | public final EventuateKafkaNativeContainer kafka;
10 |
11 | public EventuateKafkaNativeCluster() {
12 | this("foofoo");
13 | }
14 |
15 | public EventuateKafkaNativeCluster(String networkName) {
16 | network = ReusableNetworkFactory.createNetwork(networkName);
17 |
18 | kafka = (EventuateKafkaNativeContainer)new EventuateKafkaNativeContainer()
19 | .withNetwork(network)
20 | ;
21 | }
22 |
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | zookeeper:
4 | image: eventuateio/eventuate-zookeeper:$EVENTUATE_COMMON_VERSION
5 | ports:
6 | - 2181:2181
7 | environment:
8 | ZOOKEEPER_CLIENT_PORT: 2181
9 | kafka:
10 | image: ${KAFKA_MULTI_ARCH_IMAGE:-localhost:5002/eventuate-kafka:multi-arch-local-build}
11 | ports:
12 | - 9092:9092
13 | depends_on:
14 | - zookeeper
15 | environment:
16 | KAFKA_LISTENERS: LC://kafka:29092,LX://kafka:9092
17 | KAFKA_ADVERTISED_LISTENERS: LC://kafka:29092,LX://${DOCKER_HOST_IP:-localhost}:9092
18 | KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: LC:PLAINTEXT,LX:PLAINTEXT
19 | KAFKA_INTER_BROKER_LISTENER_NAME: LC
20 | KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
21 | KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
22 | KAFKA_ZOOKEEPER_CONNECTION_TIMEOUT_MS: 60000
23 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-micronaut-producer/src/main/java/io/eventuate/messaging/kafka/micronaut/producer/EventuateKafkaProducerConfigurationPropertiesFactory.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.micronaut.producer;
2 |
3 | import io.eventuate.messaging.kafka.producer.EventuateKafkaProducerConfigurationProperties;
4 | import io.micronaut.context.annotation.Factory;
5 |
6 | import javax.inject.Singleton;
7 |
8 | @Factory
9 | public class EventuateKafkaProducerConfigurationPropertiesFactory {
10 | @Singleton
11 | public EventuateKafkaProducerConfigurationProperties eventuateKafkaProducerConfigurationProperties(EventuateKafkaProducerMicronautConfigurationProperties eventuateKafkaProducerMicronautConfigurationProperties) {
12 | return new EventuateKafkaProducerConfigurationProperties(eventuateKafkaProducerMicronautConfigurationProperties.getProperties());
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-testcontainers/build.gradle:
--------------------------------------------------------------------------------
1 | dependencies {
2 | implementation "io.eventuate.common:eventuate-common-testcontainers:$eventuateCommonVersion"
3 | implementation "org.testcontainers:testcontainers:$testContainersVersion"
4 | api "org.testcontainers:kafka:$testContainersVersion"
5 |
6 | implementation "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
7 | implementation "org.assertj:assertj-core:$assertjVersion"
8 | testImplementation project(":eventuate-messaging-kafka-spring-consumer")
9 |
10 |
11 | }
12 |
13 | task writeProperties(type: WriteProperties) {
14 | outputFile "${project.buildDir}/generated/eventuate.messaging.kafka.version.properties"
15 | property 'version', version
16 | }
17 |
18 |
19 | processResources.from(writeProperties)
20 | tasks.withType(Test).configureEach {
21 | useJUnitPlatform()
22 | }
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-integration-test/build.gradle:
--------------------------------------------------------------------------------
1 |
2 | dependencies {
3 |
4 | api project (':eventuate-messaging-kafka-common')
5 | api project (':eventuate-messaging-kafka-consumer')
6 | api project (':eventuate-messaging-kafka-producer')
7 | api "io.eventuate.util:eventuate-util-test:$eventuateUtilVersion"
8 |
9 | implementation project (":eventuate-messaging-kafka-basic-consumer")
10 | implementation "io.eventuate.common.messaging:eventuate-messaging-partition-management:$eventuateCommonMessagingVersion"
11 | implementation "org.junit.jupiter:junit-jupiter:$junitVersion"
12 |
13 | implementation "org.apache.kafka:kafka-clients:$kafkaClientVersion"
14 | implementation "org.mockito:mockito-core:4.11.0"
15 | implementation "org.assertj:assertj-core:3.23.1"
16 |
17 |
18 |
19 | }
20 | tasks.withType(Test).configureEach {
21 | useJUnitPlatform()
22 | }
23 |
--------------------------------------------------------------------------------
/kafka/build-docker-kafka-multi-arch.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash -e
2 |
3 | SCRIPT_DIR=$(cd $( dirname "${BASH_SOURCE[0]}" ) ; pwd)
4 |
5 | docker-compose -f $SCRIPT_DIR/../docker-compose-registry.yml --project-name eventuate-common-registry up -d registry
6 |
7 | IMAGE="${KAFKA_MULTI_ARCH_IMAGE:-${DOCKER_HOST_NAME:-host.docker.internal}:5002/eventuate-kafka:multi-arch-local-build}"
8 | OPTS="${BUILDX_PUSH_OPTIONS:---output=type=image,push=true,registry.insecure=true}"
9 |
10 | echo IMAGE=$IMAGE
11 | echo OPTS=$OPTS
12 |
13 | docker buildx build --platform linux/amd64,linux/arm64 \
14 | -t $IMAGE \
15 | -f $SCRIPT_DIR/Dockerfile \
16 | $OPTS \
17 | $SCRIPT_DIR
18 |
19 | echo maybe pull
20 |
21 | if [ "$IMAGE" = "host.docker.internal:5002/eventuate-kafka:multi-arch-local-build" ] ; then
22 | docker pull localhost:5002/eventuate-kafka:multi-arch-local-build
23 | else
24 | echo not pulling "$IMAGE"
25 | fi
26 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-leadership/src/main/java/io/eventuate/coordination/leadership/zookeeper/KafkaLeadershipController.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.coordination.leadership.zookeeper;
2 |
3 | import io.eventuate.coordination.leadership.LeadershipController;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 |
7 | import java.util.concurrent.CountDownLatch;
8 |
9 | public class KafkaLeadershipController implements LeadershipController {
10 | private Logger logger = LoggerFactory.getLogger(getClass());
11 |
12 | private CountDownLatch stopCountDownLatch;
13 |
14 | public KafkaLeadershipController(CountDownLatch stopCountDownLatch) {
15 | this.stopCountDownLatch = stopCountDownLatch;
16 | }
17 |
18 | @Override
19 | public void stop() {
20 | logger.info("Stopping leadership controller");
21 | stopCountDownLatch.countDown();
22 | logger.info("Stopped leadership controller");
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-spring-producer/src/main/java/io/eventuate/messaging/kafka/spring/producer/EventuateKafkaProducerSpringConfigurationProperties.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.spring.producer;
2 |
3 | import org.springframework.boot.context.properties.ConfigurationProperties;
4 |
5 | import java.util.HashMap;
6 | import java.util.Map;
7 |
8 | @ConfigurationProperties("eventuate.local.kafka.producer")
9 | public class EventuateKafkaProducerSpringConfigurationProperties {
10 | Map properties = new HashMap<>();
11 |
12 | public Map getProperties() {
13 | return properties;
14 | }
15 |
16 | public void setProperties(Map properties) {
17 | this.properties = properties;
18 | }
19 |
20 | public static EventuateKafkaProducerSpringConfigurationProperties empty() {
21 | return new EventuateKafkaProducerSpringConfigurationProperties();
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-basic-consumer/src/main/java/io/eventuate/messaging/kafka/basic/consumer/ConsumerPropertiesFactory.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.basic.consumer;
2 |
3 | import java.util.Properties;
4 |
5 | public class ConsumerPropertiesFactory {
6 | public static Properties makeDefaultConsumerProperties(String bootstrapServers, String subscriberId) {
7 | Properties consumerProperties = new Properties();
8 | consumerProperties.put("bootstrap.servers", bootstrapServers);
9 | consumerProperties.put("group.id", subscriberId);
10 | consumerProperties.put("enable.auto.commit", "false");
11 | consumerProperties.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
12 | consumerProperties.put("value.deserializer", "org.apache.kafka.common.serialization.ByteArrayDeserializer");
13 | consumerProperties.put("auto.offset.reset", "earliest");
14 | return consumerProperties;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-producer/src/main/java/io/eventuate/messaging/kafka/producer/EventuateKafkaProducerConfigurationProperties.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.producer;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 |
6 | public class EventuateKafkaProducerConfigurationProperties {
7 | Map properties = new HashMap<>();
8 |
9 | public EventuateKafkaProducerConfigurationProperties() {
10 | }
11 |
12 | public EventuateKafkaProducerConfigurationProperties(Map properties) {
13 | this.properties = properties;
14 | }
15 |
16 | public Map getProperties() {
17 | return properties;
18 | }
19 |
20 | public void setProperties(Map properties) {
21 | this.properties = properties;
22 | }
23 |
24 | public static EventuateKafkaProducerConfigurationProperties empty() {
25 | return new EventuateKafkaProducerConfigurationProperties();
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-consumer/src/test/java/io/eventuate/messaging/kafka/consumer/SwimlanePerTopicPartitionTest.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.consumer;
2 |
3 | import org.apache.kafka.common.TopicPartition;
4 | import org.junit.jupiter.api.Test;
5 |
6 | import static org.junit.jupiter.api.Assertions.*;
7 |
8 | public class SwimlanePerTopicPartitionTest {
9 |
10 | @Test
11 | public void shouldComputeSwimlane() {
12 | SwimlanePerTopicPartition mapping = new SwimlanePerTopicPartition();
13 | assertEquals(Integer.valueOf(0), mapping.toSwimlane(tp0(), "X"));
14 | assertEquals(Integer.valueOf(0), mapping.toSwimlane(tp0(), "Y"));
15 | assertEquals(Integer.valueOf(1), mapping.toSwimlane(tp1(), "Z"));
16 | }
17 |
18 | private TopicPartition tp1() {
19 | return new TopicPartition("x", 1);
20 | }
21 |
22 | private TopicPartition tp0() {
23 | return new TopicPartition("x", 0);
24 | }
25 |
26 | }
--------------------------------------------------------------------------------
/deploy-multi-arch.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash -e
2 |
3 | BRANCH=$(git rev-parse --abbrev-ref HEAD)
4 |
5 | if [[ $BRANCH == "master" ]] ; then
6 | TARGET_TAG=$(sed -e '/^version=/!d' -e 's/version=//' -e 's/-SNAPSHOT/.BUILD-SNAPSHOT/' < gradle.properties)
7 | elif [[ $BRANCH =~ RELEASE$ ]] ; then
8 | TARGET_TAG=$BRANCH
9 | elif [[ $BRANCH =~ M[0-9]+$ ]] ; then
10 | TARGET_TAG=$BRANCH
11 | elif [[ $BRANCH =~ RC[0-9]+$ ]] ; then
12 | TARGET_TAG=$BRANCH
13 | else
14 | TARGET_TAG=${BRANCH}-${CIRCLE_BUILD_NUM?}
15 | fi
16 |
17 | docker login -u ${DOCKER_USER_ID?} -p ${DOCKER_PASSWORD?}
18 |
19 | retag() {
20 | BASE=$1
21 | IMAGE=${BASE}:${MULTI_ARCH_TAG?}
22 | TARGET_IMAGE=$BASE:$TARGET_TAG
23 |
24 | echo Retagging $IMAGE $TARGET_IMAGE
25 |
26 | SOURCES=$(docker manifest inspect $IMAGE | \
27 | jq -r '.manifests[].digest | sub("^"; "'${BASE}'@")')
28 |
29 | docker buildx imagetools create -t ${TARGET_IMAGE} $SOURCES
30 | }
31 |
32 | retag "eventuateio/eventuate-kafka"
33 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-spring-producer/src/main/java/io/eventuate/messaging/kafka/spring/producer/EventuateKafkaProducerSpringConfigurationPropertiesConfiguration.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.spring.producer;
2 |
3 | import io.eventuate.messaging.kafka.producer.EventuateKafkaProducerConfigurationProperties;
4 | import org.springframework.boot.context.properties.EnableConfigurationProperties;
5 | import org.springframework.context.annotation.Bean;
6 |
7 | @EnableConfigurationProperties(EventuateKafkaProducerSpringConfigurationProperties.class)
8 | public class EventuateKafkaProducerSpringConfigurationPropertiesConfiguration {
9 |
10 | @Bean
11 | public EventuateKafkaProducerConfigurationProperties eventuateKafkaProducerConfigurationProperties(EventuateKafkaProducerSpringConfigurationProperties eventuateKafkaProducerSpringConfigurationProperties) {
12 | return new EventuateKafkaProducerConfigurationProperties(eventuateKafkaProducerSpringConfigurationProperties.getProperties());
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-basic-consumer/src/main/java/io/eventuate/messaging/kafka/basic/consumer/BackPressureManagerNormalState.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.basic.consumer;
2 |
3 | import org.apache.kafka.common.TopicPartition;
4 |
5 | import java.util.Set;
6 |
7 | public class BackPressureManagerNormalState implements BackPressureManagerState {
8 |
9 | public static BackPressureManagerStateAndActions transitionTo(Set suspendedPartitions) {
10 | return new BackPressureManagerStateAndActions(BackPressureActions.resume(suspendedPartitions), new BackPressureManagerNormalState());
11 | }
12 |
13 | @Override
14 | public BackPressureManagerStateAndActions update(Set allTopicPartitions, int backlog, BackPressureConfig backPressureConfig) {
15 | if (backlog > backPressureConfig.getHigh()) {
16 | return BackPressureManagerPausedState.transitionTo(allTopicPartitions);
17 | } else {
18 | return new BackPressureManagerStateAndActions(this);
19 | }
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name="eventuate-messaging-kafka"
2 |
3 | include 'eventuate-messaging-kafka-basic-consumer'
4 | include 'eventuate-messaging-kafka-spring-basic-consumer'
5 | //include 'eventuate-messaging-kafka-micronaut-basic-consumer'
6 | include 'eventuate-messaging-kafka-common'
7 | include 'eventuate-messaging-kafka-spring-common'
8 | //include 'eventuate-messaging-kafka-micronaut-common'
9 | include 'eventuate-messaging-kafka-consumer'
10 | include 'eventuate-messaging-kafka-spring-consumer'
11 | //include 'eventuate-messaging-kafka-micronaut-consumer'
12 | include 'eventuate-messaging-kafka-producer'
13 | include 'eventuate-messaging-kafka-spring-producer'
14 | //include 'eventuate-messaging-kafka-micronaut-producer'
15 | include 'eventuate-messaging-kafka-integration-test'
16 | include 'eventuate-messaging-kafka-spring-integration-test'
17 | //include 'eventuate-messaging-kafka-micronaut-integration-test'
18 | include 'eventuate-messaging-kafka-testcontainers'
19 | include 'eventuate-messaging-kafka-leadership'
20 | include 'eventuate-messaging-kafka-bom'
21 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-basic-consumer/src/main/java/io/eventuate/messaging/kafka/basic/consumer/BackPressureManager.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.basic.consumer;
2 |
3 | import org.apache.kafka.common.TopicPartition;
4 |
5 | import java.util.HashSet;
6 | import java.util.Set;
7 |
8 | public class BackPressureManager {
9 |
10 | private final BackPressureConfig backPressureConfig;
11 | private Set allTopicPartitions = new HashSet<>();
12 |
13 | private BackPressureManagerState state = new BackPressureManagerNormalState();
14 |
15 | public BackPressureManager(BackPressureConfig backPressureConfig) {
16 | this.backPressureConfig = backPressureConfig;
17 | }
18 |
19 | public BackPressureActions update(Set topicPartitions, int backlog) {
20 | allTopicPartitions.addAll(topicPartitions);
21 | BackPressureManagerStateAndActions stateAndActions = state.update(allTopicPartitions, backlog, backPressureConfig);
22 | this.state = stateAndActions.state;
23 | return stateAndActions.actions;
24 | }
25 |
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-consumer/src/main/java/io/eventuate/messaging/kafka/consumer/MultipleSwimlanesPerTopicPartitionMapping.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.consumer;
2 |
3 | import org.apache.kafka.common.TopicPartition;
4 |
5 | import java.util.concurrent.ConcurrentHashMap;
6 |
7 | public class MultipleSwimlanesPerTopicPartitionMapping implements TopicPartitionToSwimlaneMapping {
8 |
9 | private final int swimlanesPerTopicPartition;
10 |
11 | private final ConcurrentHashMap mapping = new ConcurrentHashMap<>();
12 |
13 | public MultipleSwimlanesPerTopicPartitionMapping(int swimlanesPerTopicPartition) {
14 | this.swimlanesPerTopicPartition = swimlanesPerTopicPartition;
15 | }
16 |
17 |
18 | @Override
19 | public Integer toSwimlane(TopicPartition topicPartition, String messageKey) {
20 | int startingSwimlane = mapping.computeIfAbsent(topicPartition, tp -> mapping.size() * swimlanesPerTopicPartition);
21 | return startingSwimlane + messageKey.hashCode() % swimlanesPerTopicPartition;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-micronaut-producer/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "io.spring.dependency-management" version "1.1.7"
3 | }
4 |
5 |
6 |
7 |
8 | dependencyManagement {
9 | imports {
10 | mavenBom "io.micronaut:micronaut-bom:$micronautVersion"
11 | }
12 | }
13 |
14 | dependencies {
15 | implementation project (":eventuate-messaging-kafka-producer")
16 |
17 | annotationProcessor "io.micronaut:micronaut-inject-java"
18 | annotationProcessor "io.micronaut:micronaut-validation"
19 | annotationProcessor "io.micronaut.configuration:micronaut-openapi"
20 | implementation "io.micronaut:micronaut-inject"
21 | implementation "io.micronaut:micronaut-validation"
22 | implementation "io.micronaut:micronaut-runtime"
23 |
24 | testAnnotationProcessor "io.micronaut:micronaut-inject-java"
25 | testImplementation "org.junit.jupiter:junit-jupiter-api"
26 | testImplementation "io.micronaut.test:micronaut-test-junit5"
27 | testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine"
28 | }
29 |
30 | test {
31 | useJUnitPlatform()
32 | }
33 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-basic-consumer/src/main/java/io/eventuate/messaging/kafka/basic/consumer/BackPressureActions.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.basic.consumer;
2 |
3 | import org.apache.kafka.common.TopicPartition;
4 |
5 | import java.util.Collections;
6 | import java.util.Set;
7 |
8 | public class BackPressureActions {
9 |
10 | public final Set pause;
11 | public final Set resume;
12 |
13 | public BackPressureActions(Set pause, Set resume) {
14 | this.pause = pause;
15 | this.resume = resume;
16 | }
17 |
18 | public static final BackPressureActions NONE = new BackPressureActions(Collections.emptySet(), Collections.emptySet());
19 |
20 | public static BackPressureActions pause(Set topicPartitions) {
21 | return new BackPressureActions(topicPartitions, Collections.emptySet());
22 | }
23 |
24 | public static BackPressureActions resume(Set topicPartitions) {
25 | return new BackPressureActions(Collections.emptySet(), topicPartitions);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-micronaut-basic-consumer/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "io.spring.dependency-management" version "1.1.7"
3 | }
4 |
5 |
6 |
7 |
8 | dependencyManagement {
9 | imports {
10 | mavenBom "io.micronaut:micronaut-bom:$micronautVersion"
11 | }
12 | }
13 |
14 | dependencies {
15 | implementation project (':eventuate-messaging-kafka-basic-consumer')
16 |
17 | annotationProcessor "io.micronaut:micronaut-inject-java"
18 | annotationProcessor "io.micronaut:micronaut-validation"
19 | annotationProcessor "io.micronaut.configuration:micronaut-openapi"
20 | implementation "io.micronaut:micronaut-inject"
21 | implementation "io.micronaut:micronaut-validation"
22 | implementation "io.micronaut:micronaut-runtime"
23 |
24 | testAnnotationProcessor "io.micronaut:micronaut-inject-java"
25 | testImplementation "org.junit.jupiter:junit-jupiter-api"
26 | testImplementation "io.micronaut.test:micronaut-test-junit5"
27 | testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine"
28 | }
29 |
30 | test {
31 | useJUnitPlatform()
32 | }
33 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-micronaut-producer/src/test/java/io/eventuate/messaging/kafka/micronaut/producer/EventuateKafkaProducerConfigurationTest.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.micronaut.producer;
2 |
3 | import io.micronaut.test.annotation.MicronautTest;
4 | import org.junit.Assert;
5 | import org.junit.jupiter.api.Test;
6 |
7 | import javax.inject.Inject;
8 |
9 | @MicronautTest(propertySources = "application.properties")
10 | public class EventuateKafkaProducerConfigurationTest {
11 |
12 | @Inject
13 | private EventuateKafkaProducerMicronautConfigurationProperties eventuateKafkaProducerConfigurationProperties;
14 |
15 | @Test
16 | public void testPropertyParsing() {
17 |
18 | Assert.assertEquals(2, eventuateKafkaProducerConfigurationProperties.getProperties().size());
19 |
20 | Assert.assertEquals("1000000", eventuateKafkaProducerConfigurationProperties.getProperties().get("buffer.memory"));
21 |
22 | Assert.assertEquals("org.apache.kafka.common.serialization.ByteArraySerializer",
23 | eventuateKafkaProducerConfigurationProperties.getProperties().get("value.serializer"));
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-micronaut-consumer/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "io.spring.dependency-management" version "1.1.7"
3 | }
4 |
5 |
6 |
7 |
8 | dependencyManagement {
9 | imports {
10 | mavenBom "io.micronaut:micronaut-bom:$micronautVersion"
11 | }
12 | }
13 |
14 | dependencies {
15 | implementation project (":eventuate-messaging-kafka-common")
16 | implementation project (":eventuate-messaging-kafka-basic-consumer")
17 | implementation project (":eventuate-messaging-kafka-consumer")
18 | implementation project (":eventuate-messaging-kafka-micronaut-common")
19 | implementation project (":eventuate-messaging-kafka-micronaut-basic-consumer")
20 |
21 | annotationProcessor "io.micronaut:micronaut-inject-java"
22 | annotationProcessor "io.micronaut:micronaut-validation"
23 | annotationProcessor "io.micronaut.configuration:micronaut-openapi"
24 | implementation "io.micronaut:micronaut-inject"
25 | implementation "io.micronaut:micronaut-validation"
26 | implementation "io.micronaut:micronaut-runtime"
27 | }
28 | tasks.withType(Test).configureEach {
29 | useJUnitPlatform()
30 | }
31 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-leadership/build.gradle:
--------------------------------------------------------------------------------
1 |
2 | dependencies {
3 | implementation "io.eventuate.common:eventuate-common-coordination-leadership:$eventuateCommonVersion"
4 | implementation "org.slf4j:slf4j-api:1.7.18"
5 | implementation project(":eventuate-messaging-kafka-basic-consumer")
6 | implementation "org.apache.kafka:kafka-clients:$kafkaClientVersion"
7 |
8 | testImplementation project(":eventuate-messaging-kafka-spring-common")
9 | testImplementation project (':eventuate-messaging-kafka-common')
10 |
11 | testImplementation "io.eventuate.common:eventuate-common-coordination-leadership-tests:$eventuateCommonVersion"
12 | testImplementation "io.eventuate.util:eventuate-util-test:$eventuateUtilVersion"
13 | testImplementation "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
14 |
15 | testImplementation "io.eventuate.common:eventuate-common-testcontainers:$eventuateCommonVersion"
16 | testImplementation project(":eventuate-messaging-kafka-testcontainers")
17 | testImplementation "org.testcontainers:testcontainers:$testContainersVersion"
18 | }
19 |
20 | test {
21 | forkEvery 1
22 | useJUnitPlatform()
23 | }
24 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-basic-consumer/src/test/java/io/eventuate/messaging/kafka/basic/consumer/TopicPartitionOffsetsTest.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.basic.consumer;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import java.util.Optional;
6 |
7 | import static org.junit.jupiter.api.Assertions.assertEquals;
8 | import static org.junit.jupiter.api.Assertions.assertFalse;
9 |
10 | public class TopicPartitionOffsetsTest {
11 |
12 | @Test
13 | public void shouldTrackOffsets() {
14 |
15 | TopicPartitionOffsets tpo = new TopicPartitionOffsets();
16 |
17 | tpo.noteUnprocessed(1);
18 | tpo.noteUnprocessed(2);
19 | tpo.noteUnprocessed(3);
20 |
21 | tpo.noteProcessed(2);
22 |
23 | assertFalse(tpo.offsetToCommit().isPresent());
24 |
25 | tpo.noteProcessed(1);
26 |
27 | assertEquals(Long.valueOf(2L), tpo.offsetToCommit().get());
28 |
29 | tpo.noteOffsetCommitted(2);
30 |
31 | assertEquals(Optional.empty(), tpo.offsetToCommit());
32 |
33 | tpo.noteProcessed(3);
34 |
35 | assertEquals(Long.valueOf(3), tpo.offsetToCommit().get());
36 |
37 | tpo.noteOffsetCommitted(3);
38 |
39 | assertEquals(Optional.empty(), tpo.offsetToCommit());
40 | }
41 |
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-micronaut-common/src/main/java/io/eventuate/messaging/kafka/micronaut/common/EventuateKafkaPropertiesFactory.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.micronaut.common;
2 |
3 | import io.eventuate.messaging.kafka.common.EventuateKafkaConfigurationProperties;
4 | import io.micronaut.context.annotation.Bean;
5 | import io.micronaut.context.annotation.Factory;
6 | import io.micronaut.context.annotation.Requires;
7 | import io.micronaut.context.annotation.Value;
8 |
9 | @Factory
10 | public class EventuateKafkaPropertiesFactory {
11 |
12 | @Bean
13 | public EventuateKafkaConfigurationProperties eventuateKafkaConfigurationProperties(@Value("${eventuatelocal.kafka.bootstrap.servers}")
14 | String bootstrapServers,
15 | @Value("${eventuatelocal.kafka.connection.validation.timeout:1000}")
16 | long connectionValidationTimeout) {
17 | return new EventuateKafkaConfigurationProperties(bootstrapServers, connectionValidationTimeout);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-spring-common/src/main/java/io/eventuate/messaging/kafka/spring/common/EventuateKafkaPropertiesConfiguration.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.spring.common;
2 |
3 | import io.eventuate.messaging.kafka.common.EventuateKafkaConfigurationProperties;
4 | import org.springframework.beans.factory.annotation.Value;
5 | import org.springframework.context.annotation.Bean;
6 | import org.springframework.context.annotation.Configuration;
7 |
8 | @Configuration
9 | public class EventuateKafkaPropertiesConfiguration {
10 |
11 |
12 |
13 | @Bean
14 | public EventuateKafkaConfigurationProperties eventuateKafkaConfigurationProperties(@Value("${eventuatelocal.kafka.bootstrap.servers}")
15 | String bootstrapServers,
16 | @Value("${eventuatelocal.kafka.connection.validation.timeout:#{1000}}")
17 | long connectionValidationTimeout) {
18 | return new EventuateKafkaConfigurationProperties(bootstrapServers, connectionValidationTimeout);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-spring-basic-consumer/src/main/java/io/eventuate/messaging/kafka/spring/basic/consumer/EventuateKafkaConsumerSpringConfigurationProperties.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.spring.basic.consumer;
2 |
3 | import io.eventuate.messaging.kafka.basic.consumer.BackPressureConfig;
4 | import org.springframework.boot.context.properties.ConfigurationProperties;
5 |
6 | import java.util.HashMap;
7 | import java.util.Map;
8 |
9 | @ConfigurationProperties("eventuate.local.kafka.consumer")
10 | public class EventuateKafkaConsumerSpringConfigurationProperties {
11 | Map properties = new HashMap<>();
12 |
13 | private BackPressureConfig backPressure = new BackPressureConfig();
14 | private long pollTimeout = 100;
15 |
16 | public BackPressureConfig getBackPressure() {
17 | return backPressure;
18 | }
19 |
20 | public void setBackPressure(BackPressureConfig backPressure) {
21 | this.backPressure = backPressure;
22 | }
23 |
24 | public long getPollTimeout() {
25 | return pollTimeout;
26 | }
27 |
28 | public void setPollTimeout(long pollTimeout) {
29 | this.pollTimeout = pollTimeout;
30 | }
31 |
32 | public Map getProperties() {
33 | return properties;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-spring-producer/src/test/java/io/eventuate/messaging/kafka/spring/producer/EventuateKafkaProducerConfigurationSpringTest.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.spring.producer;
2 |
3 | import io.eventuate.messaging.kafka.producer.EventuateKafkaProducerConfigurationProperties;
4 | import org.junit.jupiter.api.Assertions;
5 | import org.junit.jupiter.api.Test;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.boot.test.context.SpringBootTest;
8 |
9 | @SpringBootTest(classes = EventuateKafkaProducerSpringConfigurationPropertiesConfiguration.class)
10 | public class EventuateKafkaProducerConfigurationSpringTest {
11 |
12 | @Autowired
13 | private EventuateKafkaProducerConfigurationProperties eventuateKafkaProducerConfigurationProperties;
14 |
15 | @Test
16 | public void testPropertyParsing() {
17 |
18 | Assertions.assertEquals(2, eventuateKafkaProducerConfigurationProperties.getProperties().size());
19 |
20 | Assertions.assertEquals("1000000", eventuateKafkaProducerConfigurationProperties.getProperties().get("buffer.memory"));
21 |
22 | Assertions.assertEquals("org.apache.kafka.common.serialization.ByteArraySerializer",
23 | eventuateKafkaProducerConfigurationProperties.getProperties().get("value.serializer"));
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-micronaut-basic-consumer/src/main/java/io/eventuate/messaging/kafka/micronaut/basic/consumer/EventuateKafkaConsumerConfigurationPropertiesFactory.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.micronaut.basic.consumer;
2 |
3 | import io.eventuate.messaging.kafka.basic.consumer.EventuateKafkaConsumerConfigurationProperties;
4 | import io.micronaut.context.annotation.Factory;
5 |
6 | import javax.inject.Singleton;
7 |
8 | @Factory
9 | public class EventuateKafkaConsumerConfigurationPropertiesFactory {
10 | @Singleton
11 | public EventuateKafkaConsumerConfigurationProperties eventuateKafkaConsumerConfigurationProperties(EventuateKafkaConsumerMicronautConfigurationProperties eventuateKafkaConsumerMicronautConfigurationProperties) {
12 | EventuateKafkaConsumerConfigurationProperties eventuateKafkaConsumerConfigurationProperties = new EventuateKafkaConsumerConfigurationProperties(eventuateKafkaConsumerMicronautConfigurationProperties.getProperties());
13 | eventuateKafkaConsumerConfigurationProperties.setBackPressure(eventuateKafkaConsumerMicronautConfigurationProperties.getBackPressure().toBackPressureConfig());
14 | eventuateKafkaConsumerConfigurationProperties.setPollTimeout(eventuateKafkaConsumerMicronautConfigurationProperties.getPollTimeout());
15 | return eventuateKafkaConsumerConfigurationProperties;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-micronaut-integration-test/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "io.spring.dependency-management" version "1.1.7"
3 | }
4 |
5 |
6 |
7 |
8 | dependencyManagement {
9 | imports {
10 | mavenBom "io.micronaut:micronaut-bom:$micronautVersion"
11 | }
12 | }
13 |
14 | dependencies {
15 | implementation project (':eventuate-messaging-kafka-integration-test')
16 |
17 | implementation project (':eventuate-messaging-kafka-micronaut-common')
18 | implementation project (':eventuate-messaging-kafka-micronaut-basic-consumer')
19 | implementation project (':eventuate-messaging-kafka-micronaut-producer')
20 |
21 | annotationProcessor "io.micronaut:micronaut-inject-java"
22 | annotationProcessor "io.micronaut:micronaut-validation"
23 | annotationProcessor "io.micronaut.configuration:micronaut-openapi"
24 | implementation "io.micronaut:micronaut-inject"
25 | implementation "io.micronaut:micronaut-validation"
26 | implementation "io.micronaut:micronaut-runtime"
27 |
28 | testAnnotationProcessor "io.micronaut:micronaut-inject-java"
29 | testImplementation "org.junit.jupiter:junit-jupiter-api"
30 | testImplementation "io.micronaut.test:micronaut-test-junit5"
31 | testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine"
32 | }
33 |
34 | test {
35 | useJUnitPlatform()
36 | }
37 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-spring-basic-consumer/src/main/java/io/eventuate/messaging/kafka/spring/basic/consumer/EventuateKafkaConsumerSpringConfigurationPropertiesConfiguration.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.spring.basic.consumer;
2 |
3 | import io.eventuate.messaging.kafka.basic.consumer.EventuateKafkaConsumerConfigurationProperties;
4 | import org.springframework.boot.context.properties.EnableConfigurationProperties;
5 | import org.springframework.context.annotation.Bean;
6 |
7 | @EnableConfigurationProperties(EventuateKafkaConsumerSpringConfigurationProperties.class)
8 | public class EventuateKafkaConsumerSpringConfigurationPropertiesConfiguration {
9 |
10 | @Bean
11 | public EventuateKafkaConsumerConfigurationProperties eventuateKafkaConsumerConfigurationProperties(EventuateKafkaConsumerSpringConfigurationProperties eventuateKafkaConsumerSpringConfigurationProperties) {
12 | EventuateKafkaConsumerConfigurationProperties eventuateKafkaConsumerConfigurationProperties = new EventuateKafkaConsumerConfigurationProperties(eventuateKafkaConsumerSpringConfigurationProperties.getProperties());
13 | eventuateKafkaConsumerConfigurationProperties.setBackPressure(eventuateKafkaConsumerSpringConfigurationProperties.getBackPressure());
14 | eventuateKafkaConsumerConfigurationProperties.setPollTimeout(eventuateKafkaConsumerSpringConfigurationProperties.getPollTimeout());
15 | return eventuateKafkaConsumerConfigurationProperties;
16 |
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-basic-consumer/src/main/java/io/eventuate/messaging/kafka/basic/consumer/KafkaMessageConsumer.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.basic.consumer;
2 |
3 | import java.time.Duration;
4 | import java.util.Collection;
5 | import java.util.List;
6 | import java.util.Map;
7 | import java.util.Set;
8 |
9 | import org.apache.kafka.clients.consumer.ConsumerRebalanceListener;
10 | import org.apache.kafka.clients.consumer.ConsumerRecords;
11 | import org.apache.kafka.clients.consumer.OffsetAndMetadata;
12 | import org.apache.kafka.common.PartitionInfo;
13 | import org.apache.kafka.common.TopicPartition;
14 |
15 | public interface KafkaMessageConsumer {
16 |
17 | void assign(Collection topicPartitions);
18 |
19 | void seekToEnd(Collection topicPartitions);
20 |
21 | long position(TopicPartition topicPartition);
22 |
23 | void seek(TopicPartition topicPartition, long position);
24 |
25 | void subscribe(List topics);
26 |
27 | void commitOffsets(Map offsets);
28 |
29 | List partitionsFor(String topic);
30 |
31 | ConsumerRecords poll(Duration duration);
32 |
33 | void pause(Set partitions);
34 |
35 | void resume(Set partitions);
36 |
37 | void close();
38 |
39 | void close(Duration duration);
40 |
41 | void subscribe(Collection topics, ConsumerRebalanceListener callback);
42 | }
43 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-basic-consumer/src/main/java/io/eventuate/messaging/kafka/basic/consumer/EventuateKafkaConsumerConfigurationProperties.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.basic.consumer;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 |
6 | public class EventuateKafkaConsumerConfigurationProperties {
7 | private Map properties = new HashMap<>();
8 |
9 | private BackPressureConfig backPressure = new BackPressureConfig();
10 | private long pollTimeout;
11 |
12 | public BackPressureConfig getBackPressure() {
13 | return backPressure;
14 | }
15 |
16 | public void setBackPressure(BackPressureConfig backPressure) {
17 | this.backPressure = backPressure;
18 | }
19 |
20 | public long getPollTimeout() {
21 | return pollTimeout;
22 | }
23 |
24 | public void setPollTimeout(long pollTimeout) {
25 | this.pollTimeout = pollTimeout;
26 | }
27 | public Map getProperties() {
28 | return properties;
29 | }
30 |
31 | public EventuateKafkaConsumerConfigurationProperties() {
32 | }
33 |
34 | public EventuateKafkaConsumerConfigurationProperties(Map properties) {
35 | this.properties = properties;
36 | }
37 |
38 | public void setProperties(Map properties) {
39 | this.properties = properties;
40 | }
41 |
42 | public static EventuateKafkaConsumerConfigurationProperties empty() {
43 | return new EventuateKafkaConsumerConfigurationProperties();
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-consumer/src/test/java/io/eventuate/messaging/kafka/consumer/MultipleSwimlanesPerTopicPartitionMappingTest.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.consumer;
2 |
3 | import org.apache.kafka.common.TopicPartition;
4 | import org.junit.jupiter.api.Test;
5 |
6 | import static org.assertj.core.api.Assertions.assertThat;
7 |
8 | public class MultipleSwimlanesPerTopicPartitionMappingTest {
9 |
10 | @Test
11 | public void shouldMap() {
12 | int swimlanesPerTopicPartition = 4;
13 |
14 | MultipleSwimlanesPerTopicPartitionMapping mapping = new MultipleSwimlanesPerTopicPartitionMapping(swimlanesPerTopicPartition);
15 |
16 | assertSwimlaneWithinRange(mapping, tp0(), "X", 0, swimlanesPerTopicPartition);
17 | assertSwimlaneWithinRange(mapping, tp0(), "Y", 0, swimlanesPerTopicPartition);
18 | assertSwimlaneWithinRange(mapping, tp1(), "Z", swimlanesPerTopicPartition, 2 * swimlanesPerTopicPartition);
19 |
20 | }
21 |
22 | private void assertSwimlaneWithinRange(MultipleSwimlanesPerTopicPartitionMapping mapping, TopicPartition topicPartition, String key, int minInclusive, int maxExclusive) {
23 | assertThat(mapping.toSwimlane(topicPartition, key)).isGreaterThanOrEqualTo(minInclusive).isLessThan(maxExclusive);
24 | }
25 |
26 |
27 | private TopicPartition tp1() {
28 | return new TopicPartition("x", 1);
29 | }
30 |
31 | private TopicPartition tp0() {
32 | return new TopicPartition("x", 0);
33 | }
34 |
35 | }
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-basic-consumer/src/main/java/io/eventuate/messaging/kafka/basic/consumer/BackPressureManagerPausedState.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.basic.consumer;
2 |
3 | import org.apache.kafka.common.TopicPartition;
4 |
5 | import java.util.HashSet;
6 | import java.util.Set;
7 |
8 | public class BackPressureManagerPausedState implements BackPressureManagerState {
9 |
10 | private Set suspendedPartitions;
11 |
12 | public BackPressureManagerPausedState(Set pausedTopic) {
13 | this.suspendedPartitions = new HashSet<>(pausedTopic);
14 | }
15 |
16 | public static BackPressureManagerStateAndActions transitionTo(Set allTopicPartitions) {
17 | return new BackPressureManagerStateAndActions(BackPressureActions.pause(allTopicPartitions), new BackPressureManagerPausedState(allTopicPartitions));
18 | }
19 |
20 |
21 | @Override
22 | public BackPressureManagerStateAndActions update(Set allTopicPartitions, int backlog, BackPressureConfig backPressureConfig) {
23 | if (backlog <= backPressureConfig.getLow()) {
24 | return BackPressureManagerNormalState.transitionTo(suspendedPartitions);
25 | } else {
26 | Set toSuspend = new HashSet<>(allTopicPartitions);
27 | toSuspend.removeAll(suspendedPartitions);
28 | suspendedPartitions.addAll(toSuspend);
29 | return new BackPressureManagerStateAndActions(BackPressureActions.pause(toSuspend), this);
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-micronaut-consumer/src/main/java/io/eventuate/messaging/kafka/micronaut/consumer/MessageConsumerKafkaFactory.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.micronaut.consumer;
2 |
3 | import io.eventuate.messaging.kafka.basic.consumer.EventuateKafkaConsumerConfigurationProperties;
4 | import io.eventuate.messaging.kafka.basic.consumer.KafkaConsumerFactory;
5 | import io.eventuate.messaging.kafka.common.EventuateKafkaConfigurationProperties;
6 | import io.eventuate.messaging.kafka.consumer.MessageConsumerKafkaImpl;
7 | import io.eventuate.messaging.kafka.consumer.OriginalTopicPartitionToSwimlaneMapping;
8 | import io.eventuate.messaging.kafka.consumer.TopicPartitionToSwimlaneMapping;
9 | import io.micronaut.context.annotation.Factory;
10 |
11 | import javax.inject.Singleton;
12 |
13 | @Factory
14 | public class MessageConsumerKafkaFactory {
15 | private final TopicPartitionToSwimlaneMapping partitionToSwimLaneMapping = new OriginalTopicPartitionToSwimlaneMapping();
16 |
17 | @Singleton
18 | public MessageConsumerKafkaImpl messageConsumerKafka(EventuateKafkaConfigurationProperties props,
19 | EventuateKafkaConsumerConfigurationProperties eventuateKafkaConsumerConfigurationProperties,
20 | KafkaConsumerFactory kafkaConsumerFactory) {
21 | return new MessageConsumerKafkaImpl(props.getBootstrapServers(), eventuateKafkaConsumerConfigurationProperties, kafkaConsumerFactory, partitionToSwimLaneMapping);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-testcontainers/src/test/java/io/eventuate/messaging/kafka/testcontainers/EventuateKafkaClusterTest.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.testcontainers;
2 |
3 | import org.junit.jupiter.api.BeforeAll;
4 | import org.junit.jupiter.api.Test;
5 | import org.testcontainers.lifecycle.Startables;
6 |
7 | import java.util.HashMap;
8 | import java.util.Map;
9 |
10 | import static org.assertj.core.api.Assertions.assertThat;
11 |
12 | public class EventuateKafkaClusterTest {
13 |
14 | private static EventuateKafkaCluster eventuateKafkaCluster;
15 |
16 | @BeforeAll
17 | public static void startCluster() {
18 | eventuateKafkaCluster = new EventuateKafkaCluster("network-" + System.currentTimeMillis()) {
19 | @Override
20 | protected EventuateKafkaContainer makeEventuateKafkaContainer() {
21 | return EventuateKafkaContainer.makeFromDockerfile(zookeeper.getConnectionString());
22 | }
23 | }.withReuse(false);
24 |
25 | Startables.deepStart(eventuateKafkaCluster.kafka).join();
26 | }
27 |
28 | @Test
29 | public void shouldStartKafka() {
30 |
31 | }
32 |
33 | @Test
34 | public void shouldRegisterProperties() {
35 | Map properties = new HashMap<>();
36 |
37 | eventuateKafkaCluster.registerProperties(properties::put);
38 |
39 | assertThat(properties).containsKeys("eventuatelocal.kafka.bootstrap.servers",
40 | "eventuatelocal.zookeeper.connection.string");
41 | }
42 |
43 | }
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-common/src/main/java/io/eventuate/messaging/kafka/common/EventuateKafkaMultiMessage.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.common;
2 |
3 | import io.eventuate.messaging.kafka.common.sbe.MultiMessageEncoder;
4 | import org.apache.commons.lang3.builder.EqualsBuilder;
5 | import org.apache.commons.lang3.builder.HashCodeBuilder;
6 |
7 | import java.util.Collections;
8 | import java.util.List;
9 | import java.util.Objects;
10 |
11 | public class EventuateKafkaMultiMessage extends KeyValue {
12 |
13 | private List headers;
14 |
15 | public EventuateKafkaMultiMessage(String key, String value) {
16 | this(key, value, Collections.emptyList());
17 | }
18 |
19 | public EventuateKafkaMultiMessage(String key, String value, List headers) {
20 | super(key, value);
21 | this.headers = headers;
22 | }
23 |
24 |
25 | public List getHeaders() {
26 | return headers;
27 | }
28 |
29 | @Override
30 | public boolean equals(Object o) {
31 | return EqualsBuilder.reflectionEquals(this, o);
32 | }
33 |
34 | @Override
35 | public int hashCode() {
36 | return HashCodeBuilder.reflectionHashCode(this);
37 | }
38 |
39 | @Override
40 | public int estimateSize() {
41 | int headerSize = MultiMessageEncoder.MessagesEncoder.HeadersEncoder.HEADER_SIZE;
42 | int messagesSize = KeyValue.estimateSize(headers);
43 | return super.estimateSize() + headerSize + messagesSize;
44 | }
45 |
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/.github/workflows/build-and-test.yaml:
--------------------------------------------------------------------------------
1 | name: Build, and test
2 |
3 | on:
4 | push:
5 |
6 | jobs:
7 | build-and-test:
8 |
9 | runs-on: ubuntu-latest
10 |
11 | steps:
12 | - name: Checkout source
13 | uses: actions/checkout@v2
14 |
15 | - uses: actions/setup-java@v3
16 | with:
17 | java-version: '11'
18 | java-package: jdk
19 | distribution: corretto
20 |
21 | - name: Setup Gradle
22 | uses: gradle/gradle-build-action@v2
23 | with:
24 | cache-read-only: false
25 |
26 | - name: Build
27 | run: ./gradlew :eventuate-messaging-kafka-testcontainers:clean :eventuate-messaging-kafka-testcontainers:test --tests "io.eventuate.messaging.kafka.testcontainers.EventuateKafkaNativeClusterTest"
28 |
29 |
30 | - name: Save test results
31 | if: ${{ always() }}
32 | uses: actions/upload-artifact@v2
33 | with:
34 | name: test-reports
35 | path: |
36 | **/build/reports
37 | **/build/test-results
38 |
39 | - name: get container logs
40 | run: ./.github/workflows/print-container-logs.sh
41 | if: ${{ always() }}
42 |
43 | - name: Save container logs
44 | if: ${{ always() }}
45 | uses: actions/upload-artifact@v2
46 | with:
47 | name: container-logs
48 | path: ~/container-logs
49 |
50 | - name: Save test results
51 | if: ${{ always() }}
52 | uses: actions/upload-artifact@v2
53 | with:
54 | name: test-reports
55 | path: |
56 | **/build/reports
57 | **/build/test-results
58 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-micronaut-basic-consumer/src/test/java/io/eventuate/messaging/kafka/micronaut/basic/consumer/EventuateKafkaConsumerConfigurationMicronautTest.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.micronaut.basic.consumer;
2 |
3 | import io.eventuate.messaging.kafka.basic.consumer.EventuateKafkaConsumerConfigurationProperties;
4 | import io.micronaut.test.annotation.MicronautTest;
5 | import org.junit.Assert;
6 | import org.junit.jupiter.api.Test;
7 |
8 | import javax.inject.Inject;
9 |
10 | import static org.junit.Assert.assertEquals;
11 |
12 | @MicronautTest(propertySources = "application.properties")
13 | public class EventuateKafkaConsumerConfigurationMicronautTest {
14 |
15 | @Inject
16 | private EventuateKafkaConsumerConfigurationProperties eventuateKafkaConsumerConfigurationProperties;
17 |
18 | @Test
19 | public void testPropertyParsing() {
20 |
21 | Assert.assertEquals(2, eventuateKafkaConsumerConfigurationProperties.getProperties().size());
22 |
23 | Assert.assertEquals("10000", eventuateKafkaConsumerConfigurationProperties.getProperties().get("session.timeout.ms"));
24 |
25 | Assert.assertEquals("org.apache.kafka.common.serialization.StringSerializer",
26 | eventuateKafkaConsumerConfigurationProperties.getProperties().get("key.serializer"));
27 |
28 | assertEquals(5, eventuateKafkaConsumerConfigurationProperties.getBackPressure().getLow());
29 | assertEquals(100, eventuateKafkaConsumerConfigurationProperties.getBackPressure().getHigh());
30 | assertEquals(200, eventuateKafkaConsumerConfigurationProperties.getPollTimeout());
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-common/src/main/java/io/eventuate/messaging/kafka/common/EventuateKafkaMultiMessages.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.common;
2 |
3 | import org.apache.commons.lang3.builder.EqualsBuilder;
4 | import org.apache.commons.lang3.builder.ToStringBuilder;
5 |
6 | import java.util.Collections;
7 | import java.util.List;
8 | import java.util.Objects;
9 |
10 | public class EventuateKafkaMultiMessages {
11 | private List headers;
12 | private List messages;
13 |
14 | public EventuateKafkaMultiMessages(List messages) {
15 | this(Collections.emptyList(), messages);
16 | }
17 |
18 | public EventuateKafkaMultiMessages(List headers, List messages) {
19 | this.headers = headers;
20 | this.messages = messages;
21 | }
22 |
23 | public List getHeaders() {
24 | return headers;
25 | }
26 |
27 | public List getMessages() {
28 | return messages;
29 | }
30 |
31 | public int estimateSize() {
32 | return KeyValue.estimateSize(headers) + KeyValue.estimateSize(messages);
33 | }
34 |
35 | @Override
36 | public String toString() {
37 | return ToStringBuilder.reflectionToString(this);
38 | }
39 |
40 | @Override
41 | public boolean equals(Object o) {
42 | return EqualsBuilder.reflectionEquals(this, o);
43 | }
44 |
45 | @Override
46 | public int hashCode() {
47 | return Objects.hash(headers, messages);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-spring-basic-consumer/src/test/java/io/eventuate/messaging/kafka/spring/basic/consumer/EventuateKafkaConsumerConfigurationSpringTest.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.spring.basic.consumer;
2 |
3 | import io.eventuate.messaging.kafka.basic.consumer.EventuateKafkaConsumerConfigurationProperties;
4 | import org.junit.jupiter.api.Test;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.boot.test.context.SpringBootTest;
7 |
8 | import static org.junit.jupiter.api.Assertions.assertEquals;
9 |
10 | @SpringBootTest(classes = EventuateKafkaConsumerSpringConfigurationPropertiesConfiguration.class)
11 | public class EventuateKafkaConsumerConfigurationSpringTest {
12 |
13 | @Autowired
14 | private EventuateKafkaConsumerConfigurationProperties eventuateKafkaConsumerConfigurationProperties;
15 |
16 | @Test
17 | public void testPropertyParsing() {
18 |
19 | assertEquals(2, eventuateKafkaConsumerConfigurationProperties.getProperties().size());
20 |
21 | assertEquals("10000", eventuateKafkaConsumerConfigurationProperties.getProperties().get("session.timeout.ms"));
22 |
23 | assertEquals("org.apache.kafka.common.serialization.StringSerializer",
24 | eventuateKafkaConsumerConfigurationProperties.getProperties().get("key.serializer"));
25 |
26 | assertEquals(5, eventuateKafkaConsumerConfigurationProperties.getBackPressure().getLow());
27 | assertEquals(100, eventuateKafkaConsumerConfigurationProperties.getBackPressure().getHigh());
28 | assertEquals(200, eventuateKafkaConsumerConfigurationProperties.getPollTimeout());
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-common/src/main/java/io/eventuate/messaging/kafka/common/KeyValue.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.common;
2 |
3 | import org.apache.commons.lang3.builder.EqualsBuilder;
4 | import org.apache.commons.lang3.builder.ToStringBuilder;
5 |
6 | import java.util.Collection;
7 | import java.util.Objects;
8 |
9 | public class KeyValue {
10 | public static final int ESTIMATED_BYTES_PER_CHAR = 3;
11 | public static final int KEY_HEADER_SIZE = 4;
12 | public static final int VALUE_HEADER_SIZE = 4;
13 |
14 | private String key;
15 | private String value;
16 |
17 | public KeyValue(String key, String value) {
18 | this.key = key;
19 | this.value = value;
20 | }
21 |
22 | public String getKey() {
23 | return key;
24 | }
25 |
26 | public String getValue() {
27 | return value;
28 | }
29 |
30 | public int estimateSize() {
31 | int keyLength = estimatedStringSizeInBytes(key);
32 | int valueLength = estimatedStringSizeInBytes(value);
33 | return KEY_HEADER_SIZE + keyLength + VALUE_HEADER_SIZE + valueLength;
34 | }
35 |
36 | public static int estimateSize(Collection extends KeyValue> kvs) {
37 | return kvs.stream().mapToInt(KeyValue::estimateSize).sum();
38 | }
39 |
40 | private int estimatedStringSizeInBytes(String s) {
41 | return s == null ? 0 : s.length() * ESTIMATED_BYTES_PER_CHAR;
42 | }
43 |
44 | @Override
45 | public String toString() {
46 | return ToStringBuilder.reflectionToString(this);
47 | }
48 |
49 | @Override
50 | public boolean equals(Object o) {
51 | return EqualsBuilder.reflectionEquals(this, o);
52 | }
53 |
54 | @Override
55 | public int hashCode() {
56 | return Objects.hash(key, value);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-testcontainers/src/main/java/io/eventuate/messaging/kafka/testcontainers/EventuateKafkaCluster.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.testcontainers;
2 |
3 | import io.eventuate.common.testcontainers.EventuateZookeeperContainer;
4 | import io.eventuate.common.testcontainers.ReusableNetworkFactory;
5 | import org.testcontainers.containers.Network;
6 |
7 | import java.util.function.BiConsumer;
8 | import java.util.function.Supplier;
9 |
10 | public class EventuateKafkaCluster {
11 |
12 | public final Network network;
13 |
14 | public final EventuateZookeeperContainer zookeeper;
15 |
16 | public final EventuateKafkaContainer kafka;
17 |
18 | public EventuateKafkaCluster() {
19 | this("foofoo");
20 | }
21 |
22 | public EventuateKafkaCluster(String networkName) {
23 | network = ReusableNetworkFactory.createNetwork(networkName);
24 | zookeeper = new EventuateZookeeperContainer().withReuse(true)
25 | .withNetwork(network)
26 | .withNetworkAliases("zookeeper");
27 | kafka = makeEventuateKafkaContainer()
28 | .dependsOn(zookeeper)
29 | .withNetwork(network)
30 | .withNetworkAliases("kafka")
31 | .withReuse(true);
32 | }
33 |
34 | protected EventuateKafkaContainer makeEventuateKafkaContainer() {
35 | return new EventuateKafkaContainer("zookeeper:2181");
36 | }
37 |
38 |
39 | public void registerProperties(BiConsumer> registry) {
40 | zookeeper.registerProperties(registry);
41 | kafka.registerProperties(registry);
42 | }
43 |
44 | public EventuateKafkaCluster withReuse(boolean reuse) {
45 | zookeeper.withReuse(reuse);
46 | kafka.withReuse(reuse);
47 | return this;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-producer/src/main/java/io/eventuate/messaging/kafka/producer/EventuateKafkaPartitioner.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.producer;
2 |
3 | import org.apache.kafka.common.PartitionInfo;
4 | import org.apache.kafka.common.utils.Utils;
5 |
6 | import java.util.List;
7 | import java.util.concurrent.ConcurrentHashMap;
8 | import java.util.concurrent.ConcurrentMap;
9 | import java.util.concurrent.ThreadLocalRandom;
10 | import java.util.concurrent.atomic.AtomicInteger;
11 | import java.util.stream.Collectors;
12 |
13 | public class EventuateKafkaPartitioner {
14 |
15 | private final ConcurrentMap topicCounterMap = new ConcurrentHashMap<>();
16 |
17 | public int partition(String topic, byte[] keyBytes, List partitions) {
18 | int numPartitions = partitions.size();
19 | if (keyBytes == null) {
20 | int nextValue = nextValue(topic);
21 | List availablePartitions = partitions.stream().filter(p -> p.leader() != null).collect(Collectors.toList());
22 | if (availablePartitions.size() > 0) {
23 | int part = Utils.toPositive(nextValue) % availablePartitions.size();
24 | return availablePartitions.get(part).partition();
25 | } else {
26 | return Utils.toPositive(nextValue) % numPartitions;
27 | }
28 | } else {
29 | return Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions;
30 | }
31 | }
32 |
33 | private int nextValue(String topic) {
34 | AtomicInteger counter = topicCounterMap.get(topic);
35 | if (null == counter) {
36 | counter = new AtomicInteger(ThreadLocalRandom.current().nextInt());
37 | AtomicInteger currentCounter = topicCounterMap.putIfAbsent(topic, counter);
38 | if (currentCounter != null) {
39 | counter = currentCounter;
40 | }
41 | }
42 | return counter.getAndIncrement();
43 | }
44 | }
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-basic-consumer/src/main/java/io/eventuate/messaging/kafka/basic/consumer/TopicPartitionOffsets.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.basic.consumer;
2 |
3 | import org.apache.commons.lang3.builder.ToStringBuilder;
4 |
5 | import java.util.*;
6 | import java.util.stream.Collectors;
7 |
8 | /**
9 | * Tracks the offsets for a TopicPartition that are being processed or have been processed
10 | */
11 | public class TopicPartitionOffsets {
12 |
13 | /**
14 | * offsets that are being processed
15 | */
16 | private SortedSet unprocessed = new TreeSet<>();
17 |
18 | /**
19 | * offsets that have been processed
20 | */
21 |
22 | private Set processed = new HashSet<>();
23 |
24 | @Override
25 | public String toString() {
26 | return new ToStringBuilder(this)
27 | .append("unprocessed", unprocessed)
28 | .append("processed", processed)
29 | .toString();
30 | }
31 |
32 | public void noteUnprocessed(long offset) {
33 | unprocessed.add(offset);
34 | }
35 |
36 | public void noteProcessed(long offset) {
37 | processed.add(offset);
38 | }
39 |
40 | /**
41 | * @return large of all offsets that have been processed and can be committed
42 | */
43 | Optional offsetToCommit() {
44 | Long result = null;
45 | for (long x : unprocessed) {
46 | if (processed.contains(x))
47 | result = x;
48 | else
49 | break;
50 | }
51 | return Optional.ofNullable(result);
52 | }
53 |
54 | public void noteOffsetCommitted(long offset) {
55 | unprocessed = new TreeSet<>(unprocessed.stream().filter(x -> x > offset).collect(Collectors.toList()));
56 | processed = processed.stream().filter(x -> x > offset).collect(Collectors.toSet());
57 | }
58 |
59 | public Set getPending() {
60 | Set result = new HashSet<>(unprocessed);
61 | result.removeAll(processed);
62 | return result;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-basic-consumer/src/test/java/io/eventuate/messaging/kafka/basic/consumer/OffsetTrackerTest.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.basic.consumer;
2 |
3 | import org.apache.kafka.clients.consumer.OffsetAndMetadata;
4 | import org.apache.kafka.common.TopicPartition;
5 | import org.junit.jupiter.api.Test;
6 |
7 | import java.util.Map;
8 |
9 | import static java.util.Collections.singletonMap;
10 | import static org.assertj.core.api.Assertions.assertThat;
11 | import static org.junit.jupiter.api.Assertions.assertEquals;
12 |
13 | public class OffsetTrackerTest {
14 |
15 | @Test
16 | public void shouldTrackOffsets() {
17 |
18 | TopicPartition tp = new TopicPartition("x", 0);
19 |
20 | OffsetTracker offsetTracker = new OffsetTracker();
21 |
22 | offsetTracker.noteUnprocessed(tp, 1);
23 | offsetTracker.noteUnprocessed(tp, 2);
24 | offsetTracker.noteUnprocessed(tp, 3);
25 |
26 | offsetTracker.noteProcessed(tp, 2);
27 |
28 | assertThat(offsetTracker.offsetsToCommit()).isEmpty();
29 |
30 | offsetTracker.noteProcessed(tp, 1);
31 |
32 | Map otc2 = offsetTracker.offsetsToCommit();
33 |
34 | assertEquals(makeExpectedOffsets(tp, 2), otc2);
35 |
36 | offsetTracker.noteOffsetsCommitted(otc2);
37 |
38 | assertThat(offsetTracker.offsetsToCommit()).isEmpty();
39 |
40 | offsetTracker.noteProcessed(tp, 3);
41 |
42 | Map otc3 = offsetTracker.offsetsToCommit();
43 | assertEquals(makeExpectedOffsets(tp, 3), otc3);
44 |
45 | offsetTracker.noteOffsetsCommitted(otc3);
46 |
47 | assertThat(offsetTracker.offsetsToCommit()).isEmpty();
48 | }
49 |
50 | private Map makeExpectedOffsets(TopicPartition tp, int offset) {
51 | return singletonMap(tp, new OffsetAndMetadata(offset + 1, ""));
52 | }
53 |
54 | }
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-micronaut-integration-test/src/test/java/io/eventuate/messaging/kafka/basic/consumer/EventuateKafkaProducerConsumerFactory.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.basic.consumer;
2 |
3 | import io.eventuate.messaging.kafka.common.EventuateKafkaConfigurationProperties;
4 | import io.eventuate.messaging.kafka.consumer.MessageConsumerKafkaImpl;
5 | import io.eventuate.messaging.kafka.consumer.OriginalTopicPartitionToSwimlaneMapping;
6 | import io.eventuate.messaging.kafka.consumer.TopicPartitionToSwimlaneMapping;
7 | import io.eventuate.messaging.kafka.producer.EventuateKafkaProducer;
8 | import io.eventuate.messaging.kafka.producer.EventuateKafkaProducerConfigurationProperties;
9 | import io.micronaut.context.annotation.Factory;
10 |
11 | import javax.inject.Singleton;
12 |
13 | @Factory
14 | public class EventuateKafkaProducerConsumerFactory {
15 | private final TopicPartitionToSwimlaneMapping partitionToSwimLaneMapping = new OriginalTopicPartitionToSwimlaneMapping();
16 |
17 | @Singleton
18 | public EventuateKafkaProducer producer(EventuateKafkaConfigurationProperties kafkaProperties, EventuateKafkaProducerConfigurationProperties producerProperties) {
19 | return new EventuateKafkaProducer(kafkaProperties.getBootstrapServers(), producerProperties);
20 | }
21 |
22 | @Singleton
23 | public MessageConsumerKafkaImpl messageConsumerKafka(EventuateKafkaConfigurationProperties props,
24 | EventuateKafkaConsumerConfigurationProperties eventuateKafkaConsumerConfigurationProperties,
25 | KafkaConsumerFactory kafkaConsumerFactory) {
26 | return new MessageConsumerKafkaImpl(props.getBootstrapServers(), eventuateKafkaConsumerConfigurationProperties, kafkaConsumerFactory, partitionToSwimLaneMapping);
27 | }
28 |
29 | @Singleton
30 | public KafkaConsumerFactory kafkaConsumerFactory() {
31 | return new DefaultKafkaConsumerFactory();
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-testcontainers/src/main/java/io/eventuate/messaging/kafka/testcontainers/EventuateKafkaNativeContainer.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.testcontainers;
2 |
3 |
4 | import io.eventuate.common.testcontainers.PropertyProvidingContainer;
5 | import org.jetbrains.annotations.NotNull;
6 | import org.testcontainers.kafka.KafkaContainer;
7 |
8 | import java.util.function.BiConsumer;
9 | import java.util.function.Supplier;
10 |
11 | public class EventuateKafkaNativeContainer extends KafkaContainer implements PropertyProvidingContainer {
12 |
13 | private String firstNetworkAlias;
14 |
15 | public EventuateKafkaNativeContainer() {
16 | super("apache/kafka-native:3.8.0");
17 | }
18 |
19 | public EventuateKafkaNativeContainer(String imageName) {
20 | super(imageName);
21 | }
22 |
23 | @Override
24 | protected void configure() {
25 | String controllerQuorumVoters = "%s@%s:9094".formatted(getEnvMap().get("KAFKA_NODE_ID"), "localhost");
26 | withEnv("KAFKA_CONTROLLER_QUORUM_VOTERS", controllerQuorumVoters);
27 | }
28 |
29 | @Override
30 | public EventuateKafkaNativeContainer withNetworkAliases(String... aliases) {
31 | super.withNetworkAliases(aliases);
32 | this.firstNetworkAlias = aliases[0];
33 | return this;
34 | }
35 |
36 | @Override
37 | public EventuateKafkaNativeContainer withReuse(boolean reusable) {
38 | return (EventuateKafkaNativeContainer) super.withReuse(reusable);
39 | }
40 |
41 | @NotNull
42 | public String getBootstrapServersForContainer() {
43 | if (firstNetworkAlias == null)
44 | throw new IllegalStateException("First network alias not set");
45 | return firstNetworkAlias + ":9093";
46 | }
47 |
48 | @Override
49 | public void registerProperties(BiConsumer> registry) {
50 | registry.accept("eventuatelocal.kafka.bootstrap.servers", this::getBootstrapServers);
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-spring-consumer/src/main/java/io/eventuate/messaging/kafka/spring/consumer/MessageConsumerKafkaConfiguration.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.spring.consumer;
2 |
3 | import io.eventuate.messaging.kafka.consumer.OriginalTopicPartitionToSwimlaneMapping;
4 | import io.eventuate.messaging.kafka.consumer.TopicPartitionToSwimlaneMapping;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.context.annotation.Bean;
7 | import org.springframework.context.annotation.Configuration;
8 | import org.springframework.context.annotation.Import;
9 | import io.eventuate.messaging.kafka.basic.consumer.EventuateKafkaConsumerConfigurationProperties;
10 | import io.eventuate.messaging.kafka.basic.consumer.KafkaConsumerFactory;
11 | import io.eventuate.messaging.kafka.common.EventuateKafkaConfigurationProperties;
12 | import io.eventuate.messaging.kafka.consumer.MessageConsumerKafkaImpl;
13 | import io.eventuate.messaging.kafka.spring.basic.consumer.EventuateKafkaConsumerSpringConfigurationPropertiesConfiguration;
14 | import io.eventuate.messaging.kafka.spring.common.EventuateKafkaPropertiesConfiguration;
15 |
16 | @Configuration
17 | @Import({EventuateKafkaPropertiesConfiguration.class, EventuateKafkaConsumerSpringConfigurationPropertiesConfiguration.class})
18 | public class MessageConsumerKafkaConfiguration {
19 | @Autowired(required=false)
20 | private TopicPartitionToSwimlaneMapping partitionToSwimLaneMapping = new OriginalTopicPartitionToSwimlaneMapping();
21 |
22 | @Bean
23 | public MessageConsumerKafkaImpl messageConsumerKafka(EventuateKafkaConfigurationProperties props,
24 | EventuateKafkaConsumerConfigurationProperties eventuateKafkaConsumerConfigurationProperties,
25 | KafkaConsumerFactory kafkaConsumerFactory) {
26 | return new MessageConsumerKafkaImpl(props.getBootstrapServers(), eventuateKafkaConsumerConfigurationProperties, kafkaConsumerFactory, partitionToSwimLaneMapping);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-micronaut-basic-consumer/src/main/java/io/eventuate/messaging/kafka/micronaut/basic/consumer/EventuateKafkaConsumerMicronautConfigurationProperties.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.micronaut.basic.consumer;
2 |
3 | import io.eventuate.messaging.kafka.basic.consumer.BackPressureConfig;
4 | import io.micronaut.context.annotation.ConfigurationProperties;
5 |
6 | import java.util.HashMap;
7 | import java.util.Map;
8 | import java.util.stream.Collectors;
9 |
10 | @ConfigurationProperties("eventuate.local.kafka.consumer")
11 | public class EventuateKafkaConsumerMicronautConfigurationProperties {
12 | Map properties = new HashMap<>();
13 |
14 | private BackPressureProperties backPressure = new BackPressureProperties();
15 | private long pollTimeout = 100;
16 |
17 | public BackPressureProperties getBackPressure() {
18 | return backPressure;
19 | }
20 |
21 | public void setBackPressure(BackPressureProperties backPressure) {
22 | this.backPressure = backPressure;
23 | }
24 |
25 | public long getPollTimeout() {
26 | return pollTimeout;
27 | }
28 |
29 | public void setPollTimeout(long pollTimeout) {
30 | this.pollTimeout = pollTimeout;
31 | }
32 | public Map getProperties() {
33 | return properties
34 | .entrySet()
35 | .stream()
36 | .collect(Collectors.toMap(o -> o.getKey().replace("-", "."), Map.Entry::getValue));
37 | }
38 |
39 | @ConfigurationProperties("backPressure")
40 | public static class BackPressureProperties {
41 | private int low = 0;
42 | private int high = Integer.MAX_VALUE;
43 |
44 | public int getLow() {
45 | return low;
46 | }
47 |
48 | public void setLow(int low) {
49 | this.low = low;
50 | }
51 |
52 | public int getHigh() {
53 | return high;
54 | }
55 |
56 | public void setHigh(int high) {
57 | this.high = high;
58 | }
59 |
60 | public BackPressureConfig toBackPressureConfig() {
61 | return new BackPressureConfig(low, high);
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-basic-consumer/src/main/java/io/eventuate/messaging/kafka/basic/consumer/OffsetTracker.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.basic.consumer;
2 |
3 | import org.apache.commons.lang3.builder.ToStringBuilder;
4 | import org.apache.kafka.clients.consumer.OffsetAndMetadata;
5 | import org.apache.kafka.common.TopicPartition;
6 |
7 | import java.util.HashMap;
8 | import java.util.Map;
9 | import java.util.Set;
10 |
11 | /**
12 | * Keeps track of message offsets that are (a) being processed and (b) have been processed and can be committed
13 | */
14 | public class OffsetTracker {
15 |
16 | private Map state = new HashMap<>();
17 |
18 | @Override
19 | public String toString() {
20 | return new ToStringBuilder(this)
21 | .append("state", state)
22 | .toString();
23 | }
24 |
25 | private TopicPartitionOffsets fetch(TopicPartition topicPartition) {
26 | TopicPartitionOffsets tpo = state.get(topicPartition);
27 | if (tpo == null) {
28 | tpo = new TopicPartitionOffsets();
29 | state.put(topicPartition, tpo);
30 | }
31 | return tpo;
32 | }
33 | void noteUnprocessed(TopicPartition topicPartition, long offset) {
34 | fetch(topicPartition).noteUnprocessed(offset);
35 | }
36 |
37 | void noteProcessed(TopicPartition topicPartition, long offset) {
38 | fetch(topicPartition).noteProcessed(offset);
39 | }
40 |
41 | private final int OFFSET_ADJUSTMENT = 1;
42 | public Map offsetsToCommit() {
43 | Map result = new HashMap<>();
44 | state.forEach((tp, tpo) -> tpo.offsetToCommit().ifPresent(offset -> result.put(tp, new OffsetAndMetadata(offset + OFFSET_ADJUSTMENT, ""))));
45 | return result;
46 | }
47 |
48 | public void noteOffsetsCommitted(Map offsetsToCommit) {
49 | offsetsToCommit.forEach((tp, om) -> {
50 | fetch(tp).noteOffsetCommitted(om.offset() - OFFSET_ADJUSTMENT);
51 | });
52 | }
53 |
54 | public Map> getPending() {
55 | Map> result = new HashMap<>();
56 | state.forEach((tp, tpo) -> {
57 | Set pending = tpo.getPending();
58 | if (!pending.isEmpty())
59 | result.put(tp, pending);
60 | });
61 | return result;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-consumer/src/main/java/io/eventuate/messaging/kafka/consumer/SwimlaneBasedDispatcher.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.consumer;
2 |
3 | import org.apache.kafka.common.TopicPartition;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 |
7 | import java.util.concurrent.ConcurrentHashMap;
8 | import java.util.concurrent.Executor;
9 | import java.util.function.Consumer;
10 |
11 | public class SwimlaneBasedDispatcher {
12 |
13 | private static final Logger logger = LoggerFactory.getLogger(SwimlaneBasedDispatcher.class);
14 |
15 | private final ConcurrentHashMap map = new ConcurrentHashMap<>();
16 | private final Executor executor;
17 | private final String subscriberId;
18 |
19 | private final TopicPartitionToSwimlaneMapping partitionToSwimLaneMapping;
20 |
21 | public SwimlaneBasedDispatcher(String subscriberId, Executor executor, TopicPartitionToSwimlaneMapping partitionToSwimLaneMapping) {
22 | this.subscriberId = subscriberId;
23 | this.executor = executor;
24 | this.partitionToSwimLaneMapping = partitionToSwimLaneMapping;
25 | }
26 |
27 | public SwimlaneDispatcherBacklog dispatch(RawKafkaMessage message, TopicPartition topicPartition, Consumer target) {
28 | Integer swimlane = partitionToSwimLaneMapping.toSwimlane(topicPartition, message.getMessageKey());
29 | logger.trace("Dispatching to swimlane {} for {}", swimlane, message);
30 | SwimlaneDispatcher swimlaneDispatcher = getOrCreate(swimlane);
31 | return swimlaneDispatcher.dispatch(message, target);
32 | }
33 |
34 | private SwimlaneDispatcher getOrCreate(Integer swimlane) {
35 | SwimlaneDispatcher swimlaneDispatcher = map.get(swimlane);
36 | if (swimlaneDispatcher == null) {
37 | logger.trace("No dispatcher for {} {}. Attempting to create", subscriberId, swimlane);
38 | swimlaneDispatcher = new SwimlaneDispatcher(subscriberId, swimlane, executor);
39 | SwimlaneDispatcher r = map.putIfAbsent(swimlane, swimlaneDispatcher);
40 | if (r != null) {
41 | logger.trace("Using concurrently created SwimlaneDispatcher for {} {}", subscriberId, swimlane);
42 | swimlaneDispatcher = r;
43 | } else {
44 | logger.trace("Using newly created SwimlaneDispatcher for {} {}", subscriberId, swimlane);
45 | }
46 | }
47 | return swimlaneDispatcher;
48 | }
49 | }
50 |
51 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-micronaut-integration-test/src/test/java/io/eventuate/messaging/kafka/basic/consumer/EventuateKafkaBasicConsumerMicronautTest.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.basic.consumer;
2 |
3 | import io.eventuate.messaging.kafka.common.EventuateKafkaConfigurationProperties;
4 | import io.eventuate.messaging.kafka.consumer.MessageConsumerKafkaImpl;
5 | import io.eventuate.messaging.kafka.producer.EventuateKafkaProducer;
6 | import io.micronaut.context.annotation.Property;
7 | import io.micronaut.test.annotation.MicronautTest;
8 | import org.junit.jupiter.api.Test;
9 |
10 | import javax.inject.Inject;
11 |
12 | @MicronautTest
13 | @Property(name = "eventuate.local.kafka.consumer.backPressure.high", value = "3")
14 | public class EventuateKafkaBasicConsumerMicronautTest extends AbstractEventuateKafkaBasicConsumerTest {
15 |
16 | @Inject
17 | private EventuateKafkaConfigurationProperties kafkaProperties;
18 |
19 | @Inject
20 | private EventuateKafkaConsumerConfigurationProperties consumerProperties;
21 |
22 | @Inject
23 | private EventuateKafkaProducer producer;
24 |
25 | @Inject
26 | private MessageConsumerKafkaImpl consumer;
27 |
28 | @Inject
29 | private KafkaConsumerFactory kafkaConsumerFactory;
30 |
31 | @Test
32 | @Override
33 | public void shouldStopWhenHandlerThrowsException() {
34 | super.shouldStopWhenHandlerThrowsException();
35 | }
36 |
37 | @Test
38 | @Override
39 | public void shouldConsumeMessages() {
40 | super.shouldConsumeMessages();
41 | }
42 |
43 | @Test
44 | @Override
45 | public void shouldConsumeMessagesWithBackPressure() {
46 | super.shouldConsumeMessagesWithBackPressure();
47 | }
48 |
49 | @Test
50 | @Override
51 | public void shouldConsumeBatchOfMessage() {
52 | super.shouldConsumeBatchOfMessage();
53 | }
54 |
55 | @Override
56 | protected EventuateKafkaConfigurationProperties getKafkaProperties() {
57 | return kafkaProperties;
58 | }
59 |
60 | @Override
61 | protected EventuateKafkaConsumerConfigurationProperties getConsumerProperties() {
62 | return consumerProperties;
63 | }
64 |
65 | @Override
66 | protected EventuateKafkaProducer getProducer() {
67 | return producer;
68 | }
69 |
70 | @Override
71 | protected MessageConsumerKafkaImpl getConsumer() {
72 | return consumer;
73 | }
74 |
75 | @Override
76 | protected KafkaConsumerFactory getKafkaConsumerFactory() {
77 | return kafkaConsumerFactory;
78 | }
79 | }
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-testcontainers/src/test/java/io/eventuate/messaging/kafka/testcontainers/EventuateKafkaNativeClusterTest.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.testcontainers;
2 |
3 | import io.eventuate.common.testcontainers.PropertyProvidingContainer;
4 | import io.eventuate.messaging.kafka.consumer.KafkaSubscription;
5 | import io.eventuate.messaging.kafka.consumer.MessageConsumerKafkaImpl;
6 | import io.eventuate.messaging.kafka.spring.consumer.KafkaConsumerFactoryConfiguration;
7 | import io.eventuate.messaging.kafka.spring.consumer.MessageConsumerKafkaConfiguration;
8 | import org.junit.jupiter.api.Test;
9 | import org.slf4j.Logger;
10 | import org.slf4j.LoggerFactory;
11 | import org.springframework.beans.factory.annotation.Autowired;
12 | import org.springframework.boot.test.context.SpringBootTest;
13 | import org.springframework.context.annotation.Configuration;
14 | import org.springframework.context.annotation.Import;
15 | import org.springframework.test.context.DynamicPropertyRegistry;
16 | import org.springframework.test.context.DynamicPropertySource;
17 | import org.testcontainers.containers.output.Slf4jLogConsumer;
18 |
19 | import java.util.Collections;
20 | import java.util.UUID;
21 |
22 | @SpringBootTest
23 | public class EventuateKafkaNativeClusterTest {
24 |
25 | private final static Logger logger = LoggerFactory.getLogger(EventuateKafkaNativeClusterTest.class);
26 |
27 | private static final EventuateKafkaNativeCluster kafkaCluster =
28 | new EventuateKafkaNativeCluster("network-" + uuid());
29 |
30 | @DynamicPropertySource
31 | static void registerContainerProperties(DynamicPropertyRegistry registry) {
32 | kafkaCluster.kafka.withLogConsumer(new Slf4jLogConsumer(logger).withPrefix("kafka:"));
33 |
34 | PropertyProvidingContainer.startAndProvideProperties(registry, kafkaCluster.kafka);
35 | }
36 |
37 | @Configuration
38 | @Import({MessageConsumerKafkaConfiguration.class, KafkaConsumerFactoryConfiguration.class})
39 | public static class Config {
40 | }
41 |
42 | @Autowired
43 | private MessageConsumerKafkaImpl messageConsumerKafka;
44 |
45 | @Test
46 | public void shouldSubscribeToKafka() {
47 |
48 | KafkaSubscription s = messageConsumerKafka.subscribe(uuid(),
49 | Collections.singleton(uuid()),
50 | m -> {});
51 |
52 | }
53 |
54 | private static String uuid() {
55 | return UUID.randomUUID().toString();
56 | }
57 |
58 | }
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-common/src/main/resources/sbe-schema.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-consumer/src/test/java/io/eventuate/messaging/kafka/consumer/SwimlaneDispatcherTest.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.consumer;
2 |
3 | import io.eventuate.messaging.kafka.common.EventuateBinaryMessageEncoding;
4 | import io.eventuate.util.test.async.Eventually;
5 | import org.junit.jupiter.api.Assertions;
6 | import org.junit.jupiter.api.BeforeEach;
7 | import org.junit.jupiter.api.Test;
8 |
9 | import java.util.concurrent.Executors;
10 | import java.util.concurrent.atomic.AtomicInteger;
11 | import java.util.function.Consumer;
12 |
13 | public class SwimlaneDispatcherTest {
14 |
15 | private SwimlaneDispatcher swimlaneDispatcher;
16 | private AtomicInteger numberOfMessagesReceived;
17 | private Consumer handler;
18 |
19 | @BeforeEach
20 | public void init() {
21 | swimlaneDispatcher = new SwimlaneDispatcher("1", 1, Executors.newCachedThreadPool());
22 | numberOfMessagesReceived = new AtomicInteger(0);
23 | }
24 |
25 | @Test
26 | public void shouldDispatchManyMessages() {
27 | int numberOfMessagesToSend = 5;
28 |
29 | createHandler();
30 |
31 | sendMessages(numberOfMessagesToSend);
32 | assertMessageReceived(numberOfMessagesToSend);
33 | }
34 |
35 | @Test
36 | public void testShouldRestart() {
37 | int numberOfMessagesToSend = 5;
38 |
39 | createHandler();
40 |
41 | sendMessages(numberOfMessagesToSend);
42 | assertDispatcherStopped();
43 | sendMessages(numberOfMessagesToSend);
44 | assertMessageReceived(numberOfMessagesToSend * 2);
45 | }
46 |
47 | private void createHandler() {
48 | handler = msg -> {
49 | numberOfMessagesReceived.incrementAndGet();
50 | try {
51 | Thread.sleep(50);
52 | } catch (InterruptedException e) {
53 | throw new RuntimeException(e);
54 | }
55 | };
56 | }
57 |
58 | private void sendMessages(int numberOfMessagesToSend) {
59 | for (int i = 0; i < numberOfMessagesToSend; i++) {
60 | if (i > 0) {
61 | Assertions.assertTrue(swimlaneDispatcher.getRunning());
62 | }
63 | swimlaneDispatcher.dispatch(new RawKafkaMessage("", EventuateBinaryMessageEncoding.stringToBytes("")), handler);
64 | }
65 | }
66 |
67 | private void assertMessageReceived(int numberOfMessagesToSend) {
68 | Eventually.eventually(() -> Assertions.assertEquals(numberOfMessagesToSend, numberOfMessagesReceived.get()));
69 | }
70 |
71 | private void assertDispatcherStopped() {
72 | Eventually.eventually(() -> Assertions.assertFalse(swimlaneDispatcher.getRunning()));
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-testcontainers/src/test/java/io/eventuate/messaging/kafka/testcontainers/EventuateKafkaContainerTest.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.testcontainers;
2 |
3 | import io.eventuate.common.testcontainers.EventuateZookeeperContainer;
4 | import io.eventuate.common.testcontainers.PropertyProvidingContainer;
5 | import io.eventuate.messaging.kafka.consumer.KafkaSubscription;
6 | import io.eventuate.messaging.kafka.consumer.MessageConsumerKafkaImpl;
7 | import io.eventuate.messaging.kafka.spring.consumer.KafkaConsumerFactoryConfiguration;
8 | import io.eventuate.messaging.kafka.spring.consumer.MessageConsumerKafkaConfiguration;
9 | import org.junit.jupiter.api.Test;
10 | import org.springframework.beans.factory.annotation.Autowired;
11 | import org.springframework.boot.test.context.SpringBootTest;
12 | import org.springframework.context.annotation.Configuration;
13 | import org.springframework.context.annotation.Import;
14 | import org.springframework.test.context.DynamicPropertyRegistry;
15 | import org.springframework.test.context.DynamicPropertySource;
16 | import org.testcontainers.containers.Network;
17 |
18 | import java.util.Collections;
19 | import java.util.UUID;
20 |
21 | @SpringBootTest(classes = EventuateKafkaContainerTest.Config.class)
22 | public class EventuateKafkaContainerTest {
23 |
24 | public static Network network = Network.newNetwork();
25 |
26 | public static EventuateZookeeperContainer zookeeper = new EventuateZookeeperContainer().withReuse(true)
27 | .withNetwork(network)
28 | .withNetworkAliases("zookeeper");
29 |
30 | public static EventuateKafkaContainer kafka =
31 | EventuateKafkaContainer.makeFromDockerfile(zookeeper.getConnectionString())
32 | .withNetwork(network)
33 | .withNetworkAliases("kafka")
34 | .withReuse(true);
35 |
36 | @DynamicPropertySource
37 | static void registerContainerProperties(DynamicPropertyRegistry registry) {
38 | PropertyProvidingContainer.startAndProvideProperties(registry, zookeeper, kafka);
39 | }
40 |
41 | @Configuration
42 | @Import({MessageConsumerKafkaConfiguration.class, KafkaConsumerFactoryConfiguration.class})
43 | public static class Config {
44 | }
45 |
46 | @Autowired
47 | private MessageConsumerKafkaImpl messageConsumerKafka;
48 |
49 | @Test
50 | public void showStart() {
51 | System.out.println(kafka.getLogs());
52 | KafkaSubscription s = messageConsumerKafka.subscribe(UUID.randomUUID().toString(), Collections.singleton(UUID.randomUUID().toString()), m -> {
53 | });
54 | }
55 |
56 |
57 | }
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-basic-consumer/src/main/java/io/eventuate/messaging/kafka/basic/consumer/DefaultKafkaMessageConsumer.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.basic.consumer;
2 |
3 | import org.apache.kafka.clients.consumer.ConsumerRebalanceListener;
4 | import org.apache.kafka.clients.consumer.ConsumerRecords;
5 | import org.apache.kafka.clients.consumer.KafkaConsumer;
6 | import org.apache.kafka.clients.consumer.OffsetAndMetadata;
7 | import org.apache.kafka.common.PartitionInfo;
8 | import org.apache.kafka.common.TopicPartition;
9 |
10 | import java.time.Duration;
11 | import java.util.*;
12 |
13 | public class DefaultKafkaMessageConsumer implements KafkaMessageConsumer {
14 |
15 | private final KafkaConsumer delegate;
16 |
17 | public static KafkaMessageConsumer create(Properties properties) {
18 | return new DefaultKafkaMessageConsumer(new KafkaConsumer<>(properties));
19 | }
20 |
21 | private DefaultKafkaMessageConsumer(KafkaConsumer delegate) {
22 | this.delegate = delegate;
23 | }
24 |
25 | @Override
26 | public void assign(Collection topicPartitions) {
27 | delegate.assign(topicPartitions);
28 | }
29 |
30 | @Override
31 | public void seekToEnd(Collection topicPartitions) {
32 | delegate.seekToEnd(topicPartitions);
33 | }
34 |
35 | @Override
36 | public long position(TopicPartition topicPartition) {
37 | return delegate.position(topicPartition);
38 | }
39 |
40 | @Override
41 | public void seek(TopicPartition topicPartition, long position) {
42 | delegate.seek(topicPartition, position);
43 | }
44 |
45 | @Override
46 | public void subscribe(List topics) {
47 | delegate.subscribe(new ArrayList<>(topics));
48 | }
49 |
50 | @Override
51 | public void subscribe(Collection topics, ConsumerRebalanceListener callback) {
52 | delegate.subscribe(topics, callback);
53 | }
54 |
55 | @Override
56 | public void commitOffsets(Map offsets) {
57 | delegate.commitSync(offsets);
58 | }
59 |
60 | @Override
61 | public List partitionsFor(String topic) {
62 | return delegate.partitionsFor(topic);
63 | }
64 |
65 | @Override
66 | public ConsumerRecords poll(Duration duration) {
67 | return delegate.poll(duration);
68 | }
69 |
70 | @Override
71 | public void pause(Set partitions) {
72 | delegate.pause(partitions);
73 | }
74 |
75 | @Override
76 | public void resume(Set partitions) {
77 | delegate.resume(partitions);
78 | }
79 |
80 | @Override
81 | public void close() {
82 | delegate.close();
83 | }
84 |
85 | @Override
86 | public void close(Duration duration) {
87 | delegate.close(duration);
88 | }
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-basic-consumer/src/test/java/io/eventuate/messaging/kafka/basic/consumer/BackPressureManagerTest.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.basic.consumer;
2 |
3 | import org.apache.kafka.common.TopicPartition;
4 | import org.junit.jupiter.api.BeforeEach;
5 | import org.junit.jupiter.api.Test;
6 |
7 | import java.util.Collections;
8 | import java.util.HashSet;
9 | import java.util.Set;
10 |
11 | import static java.util.Collections.emptySet;
12 | import static org.junit.jupiter.api.Assertions.assertEquals;
13 |
14 | public class BackPressureManagerTest {
15 |
16 | private BackPressureConfig config = new BackPressureConfig();
17 | private BackPressureManager bpm;
18 | private Set twoTopicPartitions;
19 | private HashSet threeTopicPartitions;
20 | private TopicPartition thirdTopicPartition;
21 | private Set setOfThirdTopicPartition;
22 |
23 | @BeforeEach
24 | public void setUp() {
25 | this.config.setLow(5);
26 | this.config.setHigh(10);
27 | this.bpm = new BackPressureManager(config);
28 | this.twoTopicPartitions = new HashSet<>();
29 | twoTopicPartitions.add(new TopicPartition("x", 0));
30 | twoTopicPartitions.add(new TopicPartition("x", 1));
31 | this.threeTopicPartitions = new HashSet<>(twoTopicPartitions);
32 | thirdTopicPartition = new TopicPartition("x", 2);
33 | threeTopicPartitions.add(thirdTopicPartition);
34 | setOfThirdTopicPartition = Collections.singleton(thirdTopicPartition);
35 | }
36 |
37 | @Test
38 | public void shouldNotApplyBackPressure() {
39 | BackPressureActions actions = bpm.update(twoTopicPartitions, 10);
40 | assertEmpty(actions);
41 | }
42 |
43 | private void assertEmpty(BackPressureActions actions) {
44 | assertEquals(emptySet(), actions.pause);
45 | assertEquals(emptySet(), actions.resume);
46 | }
47 |
48 | @Test
49 | public void shouldApplyBackPressure() {
50 | BackPressureActions actions = bpm.update(twoTopicPartitions, 11);
51 | assertActionsEqual(new BackPressureActions(twoTopicPartitions, emptySet()), actions);
52 |
53 | actions = bpm.update(setOfThirdTopicPartition, 11);
54 | assertActionsEqual(new BackPressureActions(setOfThirdTopicPartition, emptySet()), actions);
55 |
56 | actions = bpm.update(setOfThirdTopicPartition, 11);
57 | assertEmpty(actions);
58 |
59 | actions = bpm.update(setOfThirdTopicPartition, 6);
60 | assertEmpty(actions);
61 |
62 | actions = bpm.update(setOfThirdTopicPartition, 5);
63 | assertActionsEqual(new BackPressureActions(emptySet(), threeTopicPartitions), actions);
64 |
65 | actions = bpm.update(twoTopicPartitions, 10);
66 | assertEmpty(actions);
67 |
68 | }
69 |
70 | private void assertActionsEqual(BackPressureActions expected, BackPressureActions actual) {
71 | assertEquals(expected.pause, actual.pause);
72 | assertEquals(expected.resume, actual.resume);
73 | }
74 |
75 | }
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-common/src/test/java/io/eventuate/messaging/kafka/common/sbe/SbeMessageSerdeTest.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.common.sbe;
2 |
3 | import org.agrona.ExpandableArrayBuffer;
4 | import org.junit.jupiter.api.Test;
5 |
6 | import java.util.Arrays;
7 | import java.util.stream.IntStream;
8 |
9 | import static org.junit.jupiter.api.Assertions.assertEquals;
10 |
11 | public class SbeMessageSerdeTest {
12 |
13 | String[] keys = new String[]{"abcd", "key2"};
14 | String[] values = new String[]{"Tim", "Value"};
15 |
16 | @Test
17 | public void shouldDoSomething() {
18 |
19 | ExpandableArrayBuffer buffer = encode();
20 |
21 | decode(buffer);
22 | }
23 |
24 | private ExpandableArrayBuffer encode() {
25 | ExpandableArrayBuffer buffer = new ExpandableArrayBuffer(1000);
26 |
27 | MessageHeaderEncoder messageHeaderEncoder = new MessageHeaderEncoder();
28 |
29 | MultiMessageEncoder multiMessageEncoder = new MultiMessageEncoder().wrapAndApplyHeader(buffer, 0, messageHeaderEncoder);
30 |
31 | MultiMessageEncoder.MessagesEncoder messagesEncoder = multiMessageEncoder.messagesCount(keys.length);
32 |
33 | IntStream.range(0, keys.length).forEach(idx ->
34 | messagesEncoder
35 | .next().key(keys[idx]).value(values[idx])
36 | );
37 |
38 |
39 | int messageLength = MessageHeaderEncoder.ENCODED_LENGTH + multiMessageEncoder.encodedLength();
40 | System.out.println("Encoded length=" + messageLength);
41 |
42 | // messageHeaderEncoder.wrap(buffer, 0).magicBytes(0, (short)10);
43 |
44 | ExpandableArrayBuffer buffer1 = new ExpandableArrayBuffer(1000);
45 | buffer1.putBytes(0, buffer, 0, messageLength);
46 | return buffer1;
47 | }
48 |
49 | private void decode(ExpandableArrayBuffer buffer) {
50 | MessageHeaderDecoder messageHeaderDecoder = new MessageHeaderDecoder();
51 |
52 | messageHeaderDecoder.wrap(buffer, 0);
53 |
54 | final int templateId = messageHeaderDecoder.templateId();
55 | final int actingBlockLength = messageHeaderDecoder.blockLength();
56 | final int actingVersion = messageHeaderDecoder.version();
57 |
58 |
59 | MultiMessageDecoder multiMessageDecoder = new MultiMessageDecoder().wrap(buffer, messageHeaderDecoder.encodedLength(), actingBlockLength, actingVersion);
60 |
61 | MultiMessageDecoder.MessagesDecoder messages = multiMessageDecoder.messages();
62 | int count = messages.count();
63 |
64 | assertEquals(keys.length, count);
65 |
66 | for (int i = 0; i < count; i++) {
67 | messages.next();
68 | int keyLength = messages.keyLength();
69 | byte[] keyBytes = new byte[keyLength];
70 | messages.getKey(keyBytes, 0, keyLength);
71 | int valueLength = messages.valueLength();
72 | byte[] valueBytes = new byte[valueLength];
73 | messages.getKey(valueBytes, 0, valueLength);
74 |
75 | String key = new String(keyBytes);
76 | String value = new String(valueBytes);
77 |
78 | assertEquals(keys[i], key);
79 | assertEquals(values[i], value);
80 |
81 |
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-spring-integration-test/src/test/java/io/eventuate/messaging/kafka/spring/basic/consumer/EventuateKafkaBasicConsumerSpringWithSwimlanePerTopicTest.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.spring.basic.consumer;
2 |
3 | import io.eventuate.messaging.kafka.basic.consumer.AbstractEventuateKafkaBasicConsumerTest;
4 | import io.eventuate.messaging.kafka.basic.consumer.EventuateKafkaConsumerConfigurationProperties;
5 | import io.eventuate.messaging.kafka.basic.consumer.KafkaConsumerFactory;
6 | import io.eventuate.messaging.kafka.common.EventuateKafkaConfigurationProperties;
7 | import io.eventuate.messaging.kafka.consumer.MessageConsumerKafkaImpl;
8 | import io.eventuate.messaging.kafka.consumer.SwimlanePerTopicPartition;
9 | import io.eventuate.messaging.kafka.producer.EventuateKafkaProducer;
10 | import org.junit.jupiter.api.Test;
11 | import org.springframework.beans.factory.annotation.Autowired;
12 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
13 | import org.springframework.boot.test.context.SpringBootTest;
14 | import org.springframework.context.annotation.Bean;
15 | import org.springframework.context.annotation.Configuration;
16 | import org.springframework.context.annotation.Import;
17 |
18 | @SpringBootTest(classes = EventuateKafkaBasicConsumerSpringWithSwimlanePerTopicTest.Config.class,
19 | properties = "eventuate.local.kafka.consumer.backPressure.high=3")
20 | public class EventuateKafkaBasicConsumerSpringWithSwimlanePerTopicTest extends AbstractEventuateKafkaBasicConsumerTest {
21 |
22 | @Configuration
23 | @EnableAutoConfiguration
24 | @Import(EventuateKafkaBasicConsumerSpringTest.EventuateKafkaConsumerTestConfiguration.class)
25 | public static class Config {
26 | @Bean
27 | public SwimlanePerTopicPartition swimlanePerTopicPartition() {
28 | return new SwimlanePerTopicPartition();
29 | }
30 | }
31 |
32 | @Autowired
33 | private EventuateKafkaConfigurationProperties kafkaProperties;
34 |
35 | @Autowired
36 | private EventuateKafkaConsumerConfigurationProperties consumerProperties;
37 |
38 | @Autowired
39 | private EventuateKafkaProducer producer;
40 |
41 | @Autowired
42 | private MessageConsumerKafkaImpl consumer;
43 |
44 | @Autowired
45 | private KafkaConsumerFactory kafkaConsumerFactory;
46 | @Override
47 | protected EventuateKafkaConfigurationProperties getKafkaProperties() {
48 | return kafkaProperties;
49 | }
50 |
51 | @Override
52 | protected EventuateKafkaConsumerConfigurationProperties getConsumerProperties() {
53 | return consumerProperties;
54 | }
55 |
56 | @Override
57 | protected EventuateKafkaProducer getProducer() {
58 | return producer;
59 | }
60 |
61 | @Override
62 | protected MessageConsumerKafkaImpl getConsumer() {
63 | return consumer;
64 | }
65 |
66 | @Override
67 | protected KafkaConsumerFactory getKafkaConsumerFactory() {
68 | return kafkaConsumerFactory;
69 | }
70 |
71 | @Test
72 | @Override
73 | public void shouldConsumeMessages() {
74 | super.shouldConsumeMessages();
75 | }
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/eventuate-messaging-kafka-producer/src/main/java/io/eventuate/messaging/kafka/producer/EventuateKafkaProducer.java:
--------------------------------------------------------------------------------
1 | package io.eventuate.messaging.kafka.producer;
2 |
3 | import io.eventuate.messaging.kafka.common.EventuateBinaryMessageEncoding;
4 | import org.apache.kafka.clients.producer.KafkaProducer;
5 | import org.apache.kafka.clients.producer.Producer;
6 | import org.apache.kafka.clients.producer.ProducerRecord;
7 | import org.apache.kafka.common.PartitionInfo;
8 | import org.apache.kafka.common.serialization.StringSerializer;
9 |
10 | import java.time.Duration;
11 | import java.util.List;
12 | import java.util.Properties;
13 | import java.util.concurrent.CompletableFuture;
14 |
15 | public class EventuateKafkaProducer {
16 |
17 | private final Producer producer;
18 | private final StringSerializer stringSerializer = new StringSerializer();
19 | private final EventuateKafkaPartitioner eventuateKafkaPartitioner = new EventuateKafkaPartitioner();
20 |
21 | public EventuateKafkaProducer(String bootstrapServers,
22 | EventuateKafkaProducerConfigurationProperties eventuateKafkaProducerConfigurationProperties) {
23 |
24 | Properties producerProps = new Properties();
25 | producerProps.put("bootstrap.servers", bootstrapServers);
26 | producerProps.put("enable.idempotence", "true");
27 | producerProps.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
28 | producerProps.put("value.serializer", "org.apache.kafka.common.serialization.ByteArraySerializer");
29 | producerProps.putAll(eventuateKafkaProducerConfigurationProperties.getProperties());
30 | producer = new KafkaProducer<>(producerProps);
31 | }
32 |
33 | public CompletableFuture> send(String topic, String key, String body) {
34 | return send(topic, key, EventuateBinaryMessageEncoding.stringToBytes(body));
35 | }
36 |
37 | public CompletableFuture> send(String topic, int partition, String key, String body) {
38 | return send(topic, partition, key, EventuateBinaryMessageEncoding.stringToBytes(body));
39 | }
40 |
41 | public CompletableFuture> send(String topic, String key, byte[] bytes) {
42 | return send(new ProducerRecord<>(topic, key, bytes));
43 | }
44 |
45 | public CompletableFuture> send(String topic, int partition, String key, byte[] bytes) {
46 | return send(new ProducerRecord<>(topic, partition, key, bytes));
47 | }
48 |
49 | private CompletableFuture> send(ProducerRecord producerRecord) {
50 | CompletableFuture