Friday, July 27, 2012

Spring Integration - Session 2 - More Hello Worlds

This is a follow up to Spring Integration Session 1

The first session was a simple Hello World application using Spring Integration. I want to take it a little further by considering a few more scenarios around it

So the first change to the Hello World application is to add in a Gateway component. To quickly revisit the earlier test program:

package org.bk.si.helloworld.hw1;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.integration.Message;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.message.GenericMessage;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("helloworld.xml")
public class HelloWorldTest {
 
 @Autowired
 @Qualifier("messageChannel")
 MessageChannel messageChannel;

 @Test
 public void testHelloWorld() {
  Message<String> helloWorld = new GenericMessage<String>("Hello World");
  messageChannel.send(helloWorld);
 }
}



In the lines highlighted above, the test is dependent on a Spring Integration specific component - a Message Channel, and in the test an explicit Spring Integration Message is constructed and sent to the Message Channel. There is a little too much coupling with Spring Integration which is the Messaging System here.

A Gateway component provides a facade to the messaging system, shielding the user application(in this case the Unit test) from the details of Messaging System - the messaging channel, Message and explicit sending of a message.

An example first to illustrate how the test will look with a Gateway component in place:

package org.bk.si.helloworld.hw2;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("helloworld.xml")
public class HelloWorldTest {

 @Autowired Greeter greeter;

 @Test
 public void testHelloWorld(){
   this.greeter.sendGreeting("Hello World");
 }
}


The Greeter interface above is the Gateway component. Now that this component has been introduced there is no dependency to Spring Integration in this test - there is no mention of Message, Message Channel in the code at all.

The Gateway component is also a very simple Java Interface defined this way:

package org.bk.si.helloworld.hw2;

public interface Greeter {
 public void sendGreeting(String message);
}

So now the question is who takes care of creating the messaging and sending the message to a message channel - it is through Spring Integration configuration:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:int="http://www.springframework.org/schema/integration"
 xmlns:int-stream="http://www.springframework.org/schema/integration/stream"
 xsi:schemaLocation="http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-2.1.xsd
  http://www.springframework.org/schema/integration/stream http://www.springframework.org/schema/integration/stream/spring-integration-stream-2.1.xsd
  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">


 <int:channel id="messagesChannel"></int:channel>
 
 <int:gateway service-interface="org.bk.si.helloworld.hw2.Greeter" default-request-channel="messagesChannel"></int:gateway>
 
 <int-stream:stdout-channel-adapter channel="messagesChannel" append-newline="true"/>
 

</beans>


The highlighted line above creates the Gateway component out of the Greeter interface, a proxy is created in the background which handles everything that was being done explicitly earlier - creating the messaging and sending the message to the message channel.


Now to add a little more complexity to the Hello World sample:

Consider the following test:
package org.bk.si.helloworld.hw3;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("helloworld.xml")
public class HelloWorldTest {

 @Autowired Greeter greeter;

 @Test
 public void testHelloWorld(){
  System.out.println("Started..");
  long start = System.nanoTime();
  for (int i=0;i<10;i++){
   this.greeter.sendMessage(String.format("Hello World %d",i));
  }
  System.out.println("Completed..");
  System.out.println(String.format("Took %f ms", (System.nanoTime()-start)/10e6));
 }
}

This is same as the previous unit test, except that in this case the "Hello World" message is being dispatched 10 times. The supporting Spring Integration configuration file is the following:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:int="http://www.springframework.org/schema/integration"
 xmlns:int-stream="http://www.springframework.org/schema/integration/stream"
 xsi:schemaLocation="http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-2.1.xsd
  http://www.springframework.org/schema/integration/stream http://www.springframework.org/schema/integration/stream/spring-integration-stream-2.1.xsd
  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">


 <int:publish-subscribe-channel id="messagesChannel"/>
 <int:gateway service-interface="org.bk.si.helloworld.hw3.Greeter" default-request-channel="messagesChannel"></int:gateway>
 
 
 <int-stream:stderr-channel-adapter channel="messagesChannel" append-newline="true"/>
 <int-stream:stdout-channel-adapter channel="messagesChannel" append-newline="true"/>
 
</beans>


If I run this test now, the output is along these lines:


The lines in red are being printed to syserr and in black are being printed to sysout.

So the question is why are some of them going to sysout and some of them going to syserr and why not to both?

The answer is because of the type of channel - "messagesChannel" above is a "Direct Channel" in the Spring Integration terminology and has "Point to point" semantics. The point-to-point semantics basically means that when a message comes into the Messaging Channel, only 1 receiver gets the message - so in this case either the standard out adapter OR the standard err adapter ends up printing the message that is coming into the message channel.

So to print to both adapters, the fix is to simply change the semantics of the channel - instead of a Point to Point channel, make it a Publish-Subscribe channel, which is a channel broadcasting out a message to multiple receivers. The change is very simple using Spring Integration:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:int="http://www.springframework.org/schema/integration"
 xmlns:int-stream="http://www.springframework.org/schema/integration/stream"
 xsi:schemaLocation="http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-2.1.xsd
  http://www.springframework.org/schema/integration/stream http://www.springframework.org/schema/integration/stream/spring-integration-stream-2.1.xsd
  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">


 <int:publish-subscribe-channel id="messagesChannel"/>
 <int:gateway service-interface="org.bk.si.helloworld.hw3.Greeter" default-request-channel="messagesChannel"></int:gateway>
 
 
 <int-stream:stderr-channel-adapter channel="messagesChannel" append-newline="true"/>
 <int-stream:stdout-channel-adapter channel="messagesChannel" append-newline="true"/>
 
</beans>


The output now will be the messages being printed to BOTH sysout and syserr

So this completes the introduction to a Gateway component, Direct Channel and Publish Subscribe channel.








3 comments:

  1. Nice tutorials Biju thanks. I like the way you're gradually introducing the Enterprise Integration Patterns, demonstrating the concepts.

    I think there's a slight typo in your penultimate code sample, where you introduce the publish-subscribe-channel one stage early. Otherwise great stuff, looking forward to session 3 :)

    ReplyDelete
  2. Thanks for this tutorials, they really help to get a first contact with spring integration

    ReplyDelete
  3. Hi

    there is a mistake in first example of printing Hello World 10-times:
    there should be
    which has default point-to-point behavior
    instead of

    ReplyDelete