0

What I want: Build a configurable library that

  • uses another library that has an internal routing and a subscribe method like: clientInstance.subscribe(endpoint, (endpoint, message) -> <handler>) , e.g. Paho MQTT library
  • later in my code I want to access the messages in a Flux.

My idea:

  • create MessageChannels like so:

    integrationFlowContext
        .registration(IntegrationFlows.from("message-channel:" + endpoint)).bridge().get())
        .register()
    
  • forward to reactive publishers:

    applicationContext.registerBean(
         "publisher:" + endpoint,
         Publisher.class,
         () -> IntegrationFlows.from("message-channel:" +     endpoint)).toReactivePublisher()
        );
    
  • keep the message channels in a set or similar and implement the above handler: (endpoint, message) -> messageChannels.get(endpoint).send( <converter>(message))

  • later use (in a @PostConstruct method):

    Flux
       .from((Publihser<Message<?>>)applicationContext.getBean("publisher:" + enpoint))
       .map(...)
       .subscribe()
    

I doubt this to be the best way to do what I want. Feels like abusing spring integration. Any suggestions are welcome at this point.

In general however (at least in my tests) this seemed to be working. But when I run my application, I get errors like: "Caused by: org.springframework.messaging.core.DestinationResolutionException: no output-channel or replyChannel header available".

This is especially bad, since after this exception the publishers claim to not have a subscriber anymore. Thus, in a real application no messages are proceeded anymore.

I am not sure what this message means, but I can kind of reproduce it (but don't understand why):

@Test
public void channelTest() {
    integrationFlowContext
            .registration(
                    IntegrationFlows.from("any-channel").bridge().get()
            )
            .register();

    registryUtil.registerBean(
            "any-publisher",
            Publisher.class,
            () -> IntegrationFlows.from("any-channel").toReactivePublisher()
    );

    Flux
            .from((Publisher<Message<?>>) applicationContext.getBean("any-publisher"))
            .subscribe(System.out::println);

    MessageChannel messageChannel = applicationContext.getBean("any-channel", MessageChannel.class);
    try {
        messageChannel.send(MessageBuilder.withPayload("test").build());
    } catch (Throwable t) {
        log.error("Error: ", t);
    }

}

I of course read parts of the spring integration documentation, but don't quite get what happens behind the scenes. Thus, I feel like guessing possible error causes.

EDIT:

This, however works:

@TestConfiguration
static class Config {

    GenericApplicationContext applicationContext;
    Config(
            GenericApplicationContext applicationContext,
            IntegrationFlowContext integrationFlowContext
    ) {
        this.applicationContext = applicationContext;
        // optional here, but needed for some reason in my library,
        // since I can't find the channel beans like I will do here,
        // if I didn't register them like so:
        //integrationFlowContext
        //    .registration(
        //    IntegrationFlows.from("any-channel").bridge().get())
        //    .register();

        applicationContext.registerBean(
                "any-publisher",
                Publisher.class,
                () -> IntegrationFlows.from("any-channel").toReactivePublisher()
        );

    }

    @PostConstruct
    void connect(){
        Flux
                .from((Publisher<Message<?>>) applicationContext.getBean("any-publisher"))
                .subscribe(System.out::println);
    }

}

@Autowired
ApplicationContext applicationContext;

@Autowired
IntegrationFlowContext integrationFlowContext;

@Test
@SneakyThrows
public void channel2Test() {

    MessageChannel messageChannel = applicationContext.getBean("any-channel", MessageChannel.class);
    try {
        messageChannel.send(MessageBuilder.withPayload("test").build());
    } catch (Throwable t) {
        log.error("Error: ", t);
    }

}

Thus apparently my issue above is realted to messages arriving "too early" .. I guess?!

Artem Bilan
  • 113,505
  • 11
  • 91
  • 118
PeMa
  • 1,559
  • 18
  • 44

1 Answers1

1

No, your issue is related to round-robin dispatched on the DirectChannel for the any-channel bean name.

You define two IntegrationFlow instances starting with that channel and then you declare their own subscribers, but at runtime both of them are subscribed to the same any-channel instance. And that one comes with the round-robin balancer by default. So, one message goes to your Flux.from() subscriber, but another to that bridge() which doesn't know what to do with your message, so it tries to resolve a replyChannel header.

Therefore your solution just only with one IntegrationFlows.from("any-channel").toReactivePublisher() is correct. Although you could just do a FluxMessageChannel registration and use it from one side for regular messages sending and from other side as a reactive source for Flux.from().

Artem Bilan
  • 113,505
  • 11
  • 91
  • 118
  • Thanks a lot for the clarification, makes a lot of sense now. However, it sounds like `FluxMessageChannel` is exactly what I need. Maybe you could give me a hint, how to register it correctly (programmatically). – PeMa Oct 15 '19 at 07:30
  • 1
    The `ApplicationContext.registerBean()` is OK to use here. The `FluxMessageChannel` comes with default simple constructor. – Artem Bilan Oct 15 '19 at 07:46
  • Thank, you I'll try all that and in case open a new question. `FluxMessageChannel` so far seems to solve all the issues I've had. – PeMa Oct 15 '19 at 10:54