Add key expression support to Kafka producer

Fix #134

- Add `messageKeyExpression` producer property.

Added key expression unit test.

Added messageKeyExpression documentation.
This commit is contained in:
Henryk Konsek
2017-05-11 16:27:53 +02:00
committed by Marius Bogoevici
parent 5c70e2df43
commit 61e7936978
4 changed files with 51 additions and 1 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016 the original author or authors.
* Copyright 2016-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
package org.springframework.cloud.stream.binder.kafka.properties;
import org.springframework.expression.Expression;
import java.util.HashMap;
import java.util.Map;
@@ -23,6 +25,7 @@ import javax.validation.constraints.NotNull;
/**
* @author Marius Bogoevici
* @author Henryk Konsek
*/
public class KafkaProducerProperties {
@@ -34,6 +37,8 @@ public class KafkaProducerProperties {
private int batchTimeout;
private Expression messageKeyExpression;
private Map<String, String> configuration = new HashMap<>();
public int getBufferSize() {
@@ -69,6 +74,14 @@ public class KafkaProducerProperties {
this.batchTimeout = batchTimeout;
}
public Expression getMessageKeyExpression() {
return messageKeyExpression;
}
public void setMessageKeyExpression(Expression messageKeyExpression) {
this.messageKeyExpression = messageKeyExpression;
}
public Map<String, String> getConfiguration() {
return this.configuration;
}

View File

@@ -190,6 +190,10 @@ batchTimeout::
(Normally the producer does not wait at all, and simply sends all the messages that accumulated while the previous send was in progress.) A non-zero value may increase throughput at the expense of latency.
+
Default: `0`.
messageKeyExpression::
Expression (executed against incoming message) used to resolve the key of the produced Kafka message. For example `headers.key` or `payload.myKey`.
+
Default: `none`.
configuration::
Map with a key/value pair containing generic Kafka producer properties.
+

View File

@@ -84,6 +84,7 @@ import org.springframework.util.concurrent.ListenableFutureCallback;
* @author Gary Russell
* @author Mark Fisher
* @author Soby Chacko
* @author Henryk Konsek
*/
public class KafkaMessageChannelBinder extends
AbstractMessageChannelBinder<ExtendedConsumerProperties<KafkaConsumerProperties>,
@@ -377,6 +378,7 @@ public class KafkaMessageChannelBinder extends
DefaultKafkaProducerFactory<byte[], byte[]> producerFactory) {
super(kafkaTemplate);
setTopicExpression(new LiteralExpression(topic));
setMessageKeyExpression(producerProperties.getExtension().getMessageKeyExpression());
setBeanFactory(KafkaMessageChannelBinder.this.getBeanFactory());
if (producerProperties.isPartitioned()) {
SpelExpressionParser parser = new SpelExpressionParser();

View File

@@ -32,6 +32,7 @@ import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.serialization.ByteArrayDeserializer;
import org.apache.kafka.common.serialization.LongDeserializer;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.kafka.common.serialization.StringSerializer;
import org.assertj.core.api.Condition;
import org.junit.Ignore;
import org.junit.Test;
@@ -81,6 +82,7 @@ import static org.junit.Assert.assertTrue;
/**
* @author Soby Chacko
* @author Ilayaperumal Gopinathan
* @author Henryk Konsek
*/
public abstract class KafkaBinderTests extends PartitionCapableBinderTests<AbstractKafkaTestBinder, ExtendedConsumerProperties<KafkaConsumerProperties>,
ExtendedProducerProperties<KafkaProducerProperties>> {
@@ -464,6 +466,35 @@ public abstract class KafkaBinderTests extends PartitionCapableBinderTests<Abstr
consumerBinding.unbind();
}
@Test
@SuppressWarnings("unchecked")
public void testDynamicKeyExpression() throws Exception {
Binder binder = getBinder(createConfigurationProperties());
QueueChannel moduleInputChannel = new QueueChannel();
ExtendedProducerProperties<KafkaProducerProperties> producerProperties = createProducerProperties();
producerProperties.getExtension().getConfiguration().put("key.serializer", StringSerializer.class.getName());
producerProperties.getExtension().setMessageKeyExpression(spelExpressionParser.parseExpression("headers.key"));
ExtendedConsumerProperties<KafkaConsumerProperties> consumerProperties = createConsumerProperties();
String uniqueBindingId = UUID.randomUUID().toString();
DirectChannel moduleOutputChannel = createBindableChannel("output",
createProducerBindingProperties(producerProperties));
Binding<MessageChannel> producerBinding = binder.bindProducer("foo" + uniqueBindingId + ".0",
moduleOutputChannel, producerProperties);
Binding<MessageChannel> consumerBinding = binder.bindConsumer("foo" + uniqueBindingId + ".0", null,
moduleInputChannel, consumerProperties);
Thread.sleep(1000);
Message<?> message = MessageBuilder.withPayload("somePayload").setHeader("key", "myDynamicKey").build();
// Let the consumer actually bind to the producer before sending a msg
binderBindUnbindLatency();
moduleOutputChannel.send(message);
Message<?> inbound = receive(moduleInputChannel);
assertThat(inbound).isNotNull();
String receivedKey = new String(inbound.getHeaders().get(KafkaHeaders.RECEIVED_MESSAGE_KEY, byte[].class));
assertThat(receivedKey).isEqualTo("myDynamicKey");
producerBinding.unbind();
consumerBinding.unbind();
}
@Test
@SuppressWarnings("unchecked")
public void testCustomPartitionCountOverridesPartitioningIfLarger() throws Exception {