5

Due to the design of MQTT where you can only make a connection with a unique client id, is it possible to use the same connection to publish and subscribe in Spring Framework/Boot using Integration?

Taking this very simple example, it would connect to the MQTT broker to subscribe and get messages, but if you would want to publish a message, the first connection will disconnect and re-connect after the message is sent.

@Bean
public MqttPahoClientFactory mqttClientFactory() {
    DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
    factory.setServerURIs("tcp://localhost:1883");
    factory.setUserName("guest");
    factory.setPassword("guest");
    return factory;
}

// publisher

@Bean
public IntegrationFlow mqttOutFlow() {
    return IntegrationFlows.from(CharacterStreamReadingMessageSource.stdin(),
                    e -> e.poller(Pollers.fixedDelay(1000)))
            .transform(p -> p + " sent to MQTT")
            .handle(mqttOutbound())
            .get();
}

@Bean
public MessageHandler mqttOutbound() {
    MqttPahoMessageHandler messageHandler = new MqttPahoMessageHandler("siSamplePublisher", mqttClientFactory());
    messageHandler.setAsync(true);
    messageHandler.setDefaultTopic("siSampleTopic");
    return messageHandler;
}

// consumer

@Bean
public IntegrationFlow mqttInFlow() {
    return IntegrationFlows.from(mqttInbound())
            .transform(p -> p + ", received from MQTT")
            .handle(logger())
            .get();
}

private LoggingHandler logger() {
    LoggingHandler loggingHandler = new LoggingHandler("INFO");
    loggingHandler.setLoggerName("siSample");
    return loggingHandler;
}

@Bean
public MessageProducerSupport mqttInbound() {
    MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter("siSampleConsumer",
            mqttClientFactory(), "siSampleTopic");
    adapter.setCompletionTimeout(5000);
    adapter.setConverter(new DefaultPahoMessageConverter());
    adapter.setQos(1);
    return adapter;
}

Working with 2 separate connections becomes difficult if you need to wait for an answer/result after publishing a message...

bwillemo
  • 71
  • 2
  • 6
  • Are you asking if it's possible to create a bidirectional connection to MQTT? – apexlol Jan 25 '18 at 13:23
  • Yes, somehow... Request is published to topic "request". One of the subscribers is doing some stuff with the request. The result is sent back to another topic "response". Hopefully it's understandable? – bwillemo Jan 25 '18 at 15:47
  • Not going to lie, this sounds like a mess. The whole idea of a queue to queue pattern is to decouple the components, further reinforcing guaranteed delivery. Instead what you want to do is create a dependency between 2 queues, further more, a dependency between adapters. If i'm truely honest, i don't even think it's possible, or atleast i can't imagine how or why it would be done. Sorry man. – apexlol Jan 25 '18 at 16:18

2 Answers2

1

the first connection will disconnect and re-connect after the message is sent.

Not sure what you mean by that; both components will keep open a persistent connection.

Since the factory doesn't connect the client, the adapters do, it's not designed for using a shared client.

Using a single connection won't really help with coordination of requests/replies because the reply will still come back asynchronously on another thread.

If you have some data in the request/reply that you can use for correlation of replies to requests, you can use a BarrierMessageHandler to perform that task. See my answer here for an example; it uses the standard correlation id header, but that's not possible with MQTT, you need something in the message.

Gary Russell
  • 166,535
  • 14
  • 146
  • 179
  • Hi Gary! Not sure if I understood correctly? Are you saying that I cannot have two IntegrationFlows, one outbound (publish messages) and one inbound (receive messages) for the same client id and credentials/certificates? As it doesn't make sense to have two different clients (one to publish, the other one to subscribe) for one IoT device, right? thanks – dk7 Nov 25 '21 at 09:43
  • 1
    I am not sure what you mean; you don't publish directly a device you connect to a broker; since this answer is so old, I suggest you ask a new question with more details about what you are trying to achieve. – Gary Russell Nov 25 '21 at 13:57
1

TL;DR

The answer is no, not with the current Spring Boot MQTT Integration implementation (and maybe not even with future ones).

Answer

I'm facing the same exact situation: I need an MQTT Client to be opened in both inbound and outbound, making the connection persistent and sharing the same configuration (client ID, credentials, etc.), using Spring Integration Flows as close to the design as possible.

In order to achieve this, I had to reimplement MqttPahoMessageDrivenChannelAdapter and MqttPahoMessageHandler and a Client Factory.

In both MqttPahoMessageDrivenChannelAdapter and MqttPahoMessageHandler I had to choose to use the Async one (IMqttAsyncClient) in order to fix which one to use. Then I had to review parts of code where the client instance is called/used in order to check if it was already instantiated by the other flow and checking the status (e.g. not trying to connect it if it was already connected).

Regarding the Client Factory, it was easier: I've reimplemented the getAsyncClientInstance(String url, String clientId) using the concatenation of url and clientId as hash as key to store the instance into a map that is used to retrieve the existing instance if the other flow requests it.

It somehow works, but it's just a test and I'm not even sure it's a good approach. (I've started another StackOverflow question in order to track my specific scenario).

Can you share how did you manage your situation?

iakko
  • 508
  • 2
  • 5
  • 18
  • As I couldn't really do it with Spring Integration I just manually created a MqttClient to send messages. To receiving part I moved to an external process. It's not really what I wanted, but sometimes you just need to get it to work! – bwillemo Oct 17 '19 at 10:01
  • @bwillemo: I totally understand your point, I had to implement your same way in order to solve the problem, but I don't want to give up on the "Spring way", that's why I asked you even if your question is more than one year old. Thanks anyway. – iakko Oct 17 '19 at 12:10