I have a special IntegrationFlow configured like
#Bean
public IntegrationFlow setupRabbitFlow() {
return IntegrationFlows.from(myInputChannel)
.handle((p, h) -> rabbitPublisher.publishToRabbit(p, h))
.get();
}
and some other flow that processes incoming data from some XML files, e.g. as shown here Polling from file using Java DSL - compile error when adding Files.inboundAdapter. By the end of that flow I want to pass Message to the abovementioned rabbit-sending "sink". How do I declare this?
One of the first class citizen in Spring Integration is a MessageChannel abstraction.
Any interaction between Spring Integration components (endpoints) is really done through the message channels.
What you need from your second flow is just specify .channel(myInputChannel) in the end of that flow. And the result of the XML processing will be send to your first flow.
Related
My question is related to finding a best practice to include data persistence inside an integration flow while returning the Message object so that it can be further processed by the flow.
Let's consider the following flow:
#Bean
IntegrationFlow myFlow() {
return flowDefinition ->
flowDefinition
.filter(filterUnwantedMessages)
.transform(messageTransformer)
.wireTap(flow -> flow.trigger(messagePayloadPersister)) <--- here is the interesting part
.handle(terminalHandler);
}
The wide majority of cases, instead of the wireTap I have seen in some projects, a Transformer is used to persist data, which I do not particulary like, as
the name implies transformation of a message, and persistence is something else.
My wish is to find out alternatives to the wireTap, and a colleague of mine proposed using #ServiceActivator:
#Bean
IntegrationFlow myFlow() {
return flowDefinition ->
flowDefinition
.filter(filterUnwantedMessages)
.transform(messageTransformer)
.handle(messagePayloadPersister)
.handle(terminalHandler);
}
#Component
class MesssagePayloadPersister {
#ServiceActivator <--- interesting, but..
public Message handle(Message<?> msg) {
//persist the payload somewhere..
return message;
}
}
I like the flow, it looks clean now, but also I am not 100% happy with the solution, as I am mixing DSL with Spring.
Note: org.springframework.messaging.MessageHandler is not good because the handle method returns void so it is a terminal part to the flow. I need a method that returns Message object.
Is there any way to do this?
Need to understand what you are going to do with that persisted data in the future.
And what information from the message you are going to store (or the whole message at all).
See this parts of documentation - may be something will give you some ideas:
https://docs.spring.io/spring-integration/docs/5.3.2.RELEASE/reference/html/system-management.html#message-store
https://docs.spring.io/spring-integration/docs/5.3.2.RELEASE/reference/html/system-management.html#metadata-store
https://docs.spring.io/spring-integration/docs/5.3.2.RELEASE/reference/html/message-transformation.html#claim-check
https://docs.spring.io/spring-integration/docs/5.3.2.RELEASE/reference/html/core.html#persistent-queuechannel-configuration
https://docs.spring.io/spring-integration/docs/5.3.2.RELEASE/reference/html/jdbc.html#jdbc-outbound-channel-adapter
With the last one you may need to consider to use a publishSubscribeChannel() of the Java DSL to be able to store in the DB and have a second subscriber to continue the flow.
I try to convert the integration cafe demo to Java 8 DSL, there are some existing examples in the official Spring Integration Samples.
I want to combine all the good parts of these examples.
Java 8 DSL instead of XML
AMQP to slipt the workflow in smaller flows.
Using Jackson to handle the payload in JSON.
The codes(not work as expected, some issues) are here.
In the official XML samples, it is easy to set up a request/reply channel in Amqp gateway. But go to Java 8 DSL, the options are missing.
And when the application, it will complain "reply channel or output channel is required".
BTW, is there any better option to debug/unit testing the Spring integration application?
Update 1: confused when I was writing the flow of coldDrinks flow.
The XML is from the original cafe-amqp project.
<!-- To receive an AMQP Message from a Queue, and respond to its reply-to address, configure an inbound-gateway. -->
<int-amqp:inbound-gateway
id="coldDrinksBarista"
request-channel="coldJsonDrinks"
queue-names="cold-drinks"
connection-factory="rabbitConnectionFactory" />
<int:chain input-channel="coldJsonDrinks">
<int:json-to-object-transformer type="org.springframework.integration.samples.cafe.OrderItem"/>
<int:service-activator method="prepareColdDrink">
<bean class="org.springframework.integration.samples.cafe.xml.Barista"/>
</int:service-activator>
<int:object-to-json-transformer content-type="text/x-json"/>
</int:chain>
How to convert this into Java DSL effectively. I added my thoughts inline
#Bean
public IntegrationFlow coldDrinksFlow(AmqpTemplate amqpTemplate) {
return IntegrationFlows
.from("coldDrinks")
.handle(
Amqp.outboundGateway(amqpTemplate)
.exchangeName(TOPIC_EXCHANGE_CAFE_DRINKS)
.routingKey(ROUTING_KEY_COLD_DRINKS)
)
.log("coldDrinksFlow")
.channel(preparedDrinksChannel())
.get();
}
#Bean
public IntegrationFlow coldDrinksBaristaFlow(ConnectionFactory connectionFactory, Barista barista) {
return IntegrationFlows
.from(Amqp.inboundGateway(connectionFactory, QUEUE_COLD_DRINKS)
.configureContainer(
c -> c.receiveTimeout(10000)
)// If setup replyChannel the below `handle` is not worked as expected.
)
.handle(OrderItem.class, (payload, headers) -> (Drink) barista.prepareColdDrink(payload))
//If adding a channel here, the flow will NOT return back the `coldDrinksFlow` will cause another exception, "its requiresReply is set to true..."
.get();
}
In my before experience, I would like to break the whole flow by the protocols(HTTP, FTP, etc) as edges(inbound at the beginning and outbound as the end) in small flows. The inbound/outbound gateway is easy to set it to work without setting a reply channel etc, it should reply by default through the original route using its built-in protocols instead of channels. In my inboundGateway RSocket example, I don't set a reply channel there, but the message returns to the roscket routes and receive by the client side(outboudGateway).
Update: Finally it works, check here. An issue I encountered here when trying to use Amqp to send and receive object message, the are some class cast exception thrown, the TypeId in headers is NOT changed when using handle etc MessageHandler, have to transform bwteen json/object like the xml based cafe-amqp did to make it finally work. What is missing here?
There is already an official Java DSL sample in that repo: https://github.com/spring-projects/spring-integration-samples/tree/master/dsl/cafe-dsl.
Yes, there is no an AMQP variant, but if you follow an Amqp factory from spring-integration-amqp, that should not be too hard to convert that XML config to Java DSL.
Not sure what is that "bad option" for testing you use, but in the spring-integration-test we provide enough utils to simplify flows testing: https://docs.spring.io/spring-integration/docs/5.3.2.RELEASE/reference/html/testing.html#testing
UPDATE
Your problem is here:
<int-amqp:outbound-gateway
id="coldDrinksBarista"
request-channel="coldDrinks"
reply-channel="preparedJsonDrinks"
exchange-name="cafe-drinks"
routing-key="drink.cold"
amqp-template="amqpTemplate" />
where your config:
#Bean
public IntegrationFlow coldDrinksFlow(AmqpTemplate amqpTemplate) {
return IntegrationFlows
.from("coldDrinks")
.handle(
Amqp.outboundGateway(amqpTemplate)
.exchangeName(TOPIC_EXCHANGE_CAFE_DRINKS)
.routingKey(ROUTING_KEY_COLD_DRINKS)
)
.get();
}
doesn't have a similar replyChannel part. That why you get that reply channel or output channel is required because you send from the route which does not expect any replies. Probably you just haven't finished to design your application for the whole logic...
My use case is notification service implementation in our project.
We have used spring with jms and its working fine with rest services. Able to send message to queue from one application and receive a message from queue in another application using JMSTemplate's convertAndSend(), convertAndReceive() functions.
Now I want to do this using spring integration's java dsl approach. We did a sample by using Jms's inboundGateway() and outboundGateway() in single application with ActiveMQ.
How to use spring integration's java dsl in different application for only sending/receiving messages?
Here is my code,
#Bean
public IntegrationFlow jmsInboundFlow() {
return IntegrationFlows
.from(Jms.inboundGateway(this.connectionFactory)
.destination("pan.outbound"))
.transform((String s) -> s.toUpperCase())
.channel("in-bound-request-channel")
.get();
}
#Bean
public IntegrationFlow jmsOutboundGatewayFlow() {
return IntegrationFlows.from("out-bound-request-channel")
.handle(Jms.outboundGateway(this.connectionFactory)
.requestDestination("pan.outbound"))
.log()
.bridge()
.get();
}
#MessagingGateway
interface EchoGateway {
#Gateway(requestChannel = "out-bound-request-channel")
String send(String message);
}
You have only to split configuration - JMS inbound adapter in one application and JMS outbound in second.
The way is exactly the same as in one application ? (Maybe I missed some information?)
I am getting up too speed with Spring Integration DSL. I am playing with the below example.
#Bean
public IntegrationFlow flow() {
return IntegrationFlows.from(INBOX)
.transform(p -> "world")
.get();
}
I am looking for the ability from this one flow to subscribe to multiple channels. I cannot find anything about this.
For example something like the below, where this flow is subscribed to different channels.
#Bean
public IntegrationFlow flow() {
return IntegrationFlows.from(INBOX).flow(INBOX2)
.transform(p -> "world")
.get();
}
That isn't possible. There is just no any Endpoint with several inputChannels.
On the other hand we don't need such a complexity since we always can bridge from one channel to another:
#Bean
#BridgeTo(INBOX)
public MessageChannel INBOX2() {
return new DirectChannel();
}
Also you can consider to use some router to always evaluate to the desired channel for output.
The MessageChannel is complex per se in the Spring Integration design, that it won't sound good to mess the endpoint logic.
In this tutorial[Receive and send multiple JMS messages in one transaction with Spring Integration Java DSL], they have described like that
however this property is not yet available to the Java DSL. Another
way to solve this problem would be to replace the message driven
channel adapter with a transactional poller, this however is also not
possible in the current Java DSL. To fix this we replaced the
jmsFactory in the outbound adapter with a jmsTemplate with the session
transacted set to true. Resulting in:
IntegrationFlows
.from(subscribableChannel())
.handle(Jms.outboundAdapter(jmsTemplate).destination(QUEUE2))
.get();
I'm still new to Spring Integration and Spring framework as a whole so please bear with me. I've been looking at this example.
https://github.com/benjaminwootton/spring-integration-examples/blob/master/target/classes/direct-channel-example.xml
https://github.com/benjaminwootton/spring-integration-examples/tree/master/src/main/java/examples/components
I'm wondering how do I or Spring trigger the method exactly?
I'm trying to do a round-robin using direct channel for my REST services. My REST services consumes messages and processes it.
I understand that with direct channel, Spring will round-robin through the subscribers but I'm not sure how Spring actually triggers that method.
Thank you for any help or advice.
The first class citizen in the Spring Integration is MessageChannel, so to allow for message (HTTP request) travel in the Integration flow, we should place message to some <channel>.
Since you say that you are in the REST service, I assume you use:
<int-http:inbound-gateway path="/path1,/path2"
request-channel="myChannel"/>
Here the myChannel is a component to where the HTTP request will be sent after conversion to the Spring Integration Message.
Of course, the MessageChannel is a pipe, when we push a thing into one side and there really should be something on the other side to poll that thing. In case of DirectChannel it is some subscriber. And we involve here the second class citizen - MessageHandler.
And if you use there something like <service-activator input-channel="myChannel" ref="foo" method="service">, the call stack may look like:
DirectChannel#send -> UnicastingDispatcher#dispatch ->
ServiceActivatingHandler#handleMessage -> MethodInvokingMessageProcessor#processMessage ->
MessagingMethodInvokerHelper#process -> foo#service
HTH