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...
Related
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 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.
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 have a trivial "Hello World" example workflow here where I want to expose an inbound-gateway web service that responds in plain text. I believe the way I route the response to myReplyChannel is incorrect.
<int:channel id="myRequestChannel"/>
<int:channel id="myReplyChannel"/>
<int-http:inbound-gateway id="myGateway"
path="/hi"
supported-methods="GET"
request-channel="myRequestChannel"
reply-channel="myReplyChannel"/>
<int:transformer input-channel="myRequestChannel"
output-channel="myReplyChannel"
expression="'Hello World!'"/>
This works when deployed, but when I first call the service I see this logged:
Adding {bridge:null} as a subscriber to the 'myReplyChannel' channel
Channel 'org.springframework.web.context.WebApplicationContext:myReplyChannel' has 1 subscriber(s).
started org.springframework.integration.endpoint.EventDrivenConsumer#4eef7503
Looks like Spring is adding in a subscriber for myReplyChannel at the last minute. I'd prefer to do it correctly myself.
Unit Test
I wrote a simple unit test to debug this..
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:hello.xml" })
public class HelloWorldTest {
#Autowired
private MessageChannel myRequestChannel;
#Test
public void test() {
myRequestChannel.send(MessageBuilder.withPayload("").build());
}
}
This errors out with:
org.springframework.messaging.MessageDeliveryException:
Dispatcher has no subscribers for channel
'org.springframework.context.support.GenericApplicationContext#64485a47.myReplyChannel'.
This reads to me like my configuration is wrong and here Spring isn't holding my hand.
Alternative Configuration:
I've tried just dropping myReplyChannel all together and it worked without anything in the logs.
<int:channel id="myRequestChannel"/>
<int-http:inbound-gateway id="myGateway"
path="/ok"
supported-methods="GET"
request-channel="myRequestChannel"/>
<int:transformer input-channel="myRequestChannel" expression="'OK'"/>
Is this the correct setup? If so, what is the reply-channel parameter for?
With this configuration, I get the following error in my unit test:
org.springframework.messaging.MessagingException:
org.springframework.messaging.core.DestinationResolutionException:
no output-channel or replyChannel header available
Adding {bridge:null} as a subscriber to the 'myReplyChannel' channel
.
debug this
There's nothing to "debug". This is just a DEBUG message from framework internals. Each request gets a dedicated replyChannelheader. Typically, you don't need a reply-channel on the gateway; the framework will automatically route to this request's reply channel header when it arrives at some component that has no output-channel (as you have found in your second test).
If you do specify a reply channel, the gateway creates a bridge internally so that any reply specifically sent there is bridged to the request's replyChannel header.
Typically, the only reason to ever specify a reply-channel is if you want do do something else with the reply (e.g. wire tap the channel to log the reply, or make the channel a publish-subscribe channel so you can send a copy of the reply somewhere else).
Your tests are failing because you are not populating the replyChannel header like the gateway does.
If you want to emulate the HTTP gateway in your test code, use a messaging gateway, or simply use a MessagingTemplate.convertSendAndReceive() - either one will correctly set up the replyChannel header in the request message.
Alternatively, use:
myRequestChannel.send(MessageBuilder.withPayload("")
.setReplyChannel(new QueueChannel())
.build());
Each request needs its own reply channel header so we know how to route the reply to the right requesting thread.
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