2

I'm wondering a way to perform a callback using StreamBridge, I want to do something similar to KafkaTemplate.send that returns a ListenableFuture.

Is it possible with spring cloud stream to publish some events using kafka binder and use a callback like onSuccess and onFailure?

example: producer.send(record, new callback { ... })

victor hugo
  • 85
  • 1
  • 1
  • 8

1 Answers1

4

You can either set sync on the producer binding and the send will wait internally on the future completion, or you can configure a recordMetadataChannel to get the results of the send asynchronously.

https://docs.spring.io/spring-cloud-stream/docs/current/reference/html/spring-cloud-stream-binder-kafka.html#kafka-producer-properties

recordMetadataChannel

The bean name of a MessageChannel to which successful send results should be sent; the bean must exist in the application context. The message sent to the channel is the sent message (after conversion, if any) with an additional header KafkaHeaders.RECORD_METADATA. The header contains a RecordMetadata object provided by the Kafka client; it includes the partition and offset where the record was written in the topic.

ResultMetadata meta = 
    sendResultMsg.getHeaders().get(KafkaHeaders.RECORD_METADATA, RecordMetadata.class)

Failed sends go the producer error channel (if configured); see Error Channels.

https://docs.spring.io/spring-cloud-stream/docs/current/reference/html/spring-cloud-stream-binder-kafka.html#kafka-error-channels

EDIT

Here's an example:

spring.cloud.stream.bindings.output-out-0.destination=dest1
spring.cloud.stream.bindings.output-out-0.producer.error-channel-enabled=true
spring.cloud.stream.kafka.bindings.output-out-0.producer.record-metadata-channel=meta
spring.cloud.stream.kafka.bindings.output-out-0.producer.configuration.[max.block.ms]=5000
spring.cloud.stream.kafka.bindings.output-out-0.producer.configuration.[request.timeout.ms]=5000
spring.cloud.stream.kafka.bindings.output-out-0.producer.configuration.[retries]=0
@SpringBootApplication
public class So72900966Application {

    public static void main(String[] args) {
        SpringApplication.run(So72900966Application.class, args);
    }

    @Bean
    ApplicationRunner runner(StreamBridge bridge) {
        return args -> {
            bridge.send("output-out-0", "foo");
            System.out.println("Delete topic dest1 from broker; then hit Enter");
            System.in.read();
            bridge.send("output-out-0", "foo");
            Thread.sleep(2_000);
        };
    }

}

@Component
class ResultHandler {

    @ServiceActivator(inputChannel = "meta")
    void meta(Message<?> result) {
        System.out.println(result.getHeaders().get(KafkaHeaders.RECORD_METADATA, RecordMetadata.class));
    }

    @ServiceActivator(inputChannel = "errorChannel")
    void errors(Message<?> error) {
        System.out.println(error);
    }

}

After the first result is received:

kafka-topics.sh --bootstrap-server localhost:9092 --delete --topic dest1

Then hit enter.

Result:

Delete topic dest1 from broker; then hit Enter
dest1-0@0
...
ErrorMessage [payload=org.springframework.integration.kafka.support.KafkaSendFailureException: ...
2022-07-07 13:36:19.185 ERROR 11735 --- [ad | producer-1] o.s.k.support.LoggingProducerListener    : Exception thrown when sending a message with key='null' and payload='byte[3]' to topic dest1:

org.apache.kafka.common.errors.UnknownTopicOrPartitionException: This server does not host this topic-partition.

Gary Russell
  • 166,535
  • 14
  • 146
  • 179
  • Hi Gary thanks for supporting, with recordMetaData configuration, I can log customize messaging on success and failure messaging on error? – victor hugo Jul 07 '22 at 16:37
  • Yes, that is correct. – Gary Russell Jul 07 '22 at 16:50
  • hi Gary thanks again for the help, could you post an example of this config using recordMetaData logging a success message and another one in case any failure occurs, thanks – victor hugo Jul 07 '22 at 16:55
  • I added an example. – Gary Russell Jul 07 '22 at 17:39
  • Thanks for the example gary, last doubt I saw in your example that you used errorChannel to capture messages that occurred in some way error, I checked and this channel is a global channel of errors, there is a way to subscribe to a channel that only receives errors from the producer that is sending the data? – victor hugo Jul 07 '22 at 18:00
  • Use `@ServiceActivator(inputChannel = "dest1.errors")` (instead of `errorChannel`). – Gary Russell Jul 07 '22 at 19:03
  • Ok thanks for supporting Gary. – victor hugo Jul 07 '22 at 19:38
  • hi Gary, one basic doubt Should we have to call streamBridge.send() method in separate thread or @Async, so that main calling thread does not get block even for millisecond? – riaz7se Aug 30 '22 at 17:55
  • It doesn't make any sense to set `sync` to true if you don't want the main thread to block; simply use the other technique to receive the result asynchronously. The main thread won't block (except for the initial fetch of topic metadata on the first send). – Gary Russell Aug 30 '22 at 18:27
  • Thank you Gary, i wanted to make sure about this - "The main thread won't block (except for the initial fetch of topic metadata on the first send)". – riaz7se Sep 08 '22 at 17:24
  • I am not sure what you want to "make sure". When publishing a record, the client needs to know which broker instance is the leader for the target partition; to get that information, it has to wait for metadata to arrive. – Gary Russell Sep 12 '22 at 13:28