Monday, January 4, 2010

Event Notification Framework with Spring Integration 2.0

Spring Integration is SpringSource's library for implementing Enterprise Integration Patterns in a Java application. I'm going to demonstrate how to use Spring Integration as an event notification framework.

I'll use the example in my previous article, Advanced Event Notification Framework with Apache Camel. In short, we're going to make a Sensor publish notification events to two Displays.

Fortunately, there is very minimal code change required to implement it using Spring Integration. My code was much more portable than I thought. ;-)

The complete application source can be downloaded/explored from: http://github.com/ceefour/eventfx.si

Tweaking Listener Interfaces


I had made a minor portability mistake before, annotating the listener interfaces with Camel-specific annotations. We can leave them out, as we can configure Spring Integration to work with POJO classes, without any annotations. (FYI, Apache Camel does too)

SyncListener.java becomes:

package com.soluvas.samples.eventfx.si;

import java.util.EventListener;

public interface SyncListener<E, R> extends EventListener {

 R update(E event);
}

AsyncListener.java becomes:

package com.soluvas.samples.eventfx.si;

import java.util.EventListener;

public interface AsyncListener<E> extends EventListener {

 void notify(E event);
}

Spring Context Configuration


The most major change is the Spring Context Configuration file.

<?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:context="http://www.springframework.org/schema/context"
    xmlns:si="http://www.springframework.org/schema/integration"
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
  http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-1.0.xsd">

 <context:component-scan base-package="com.soluvas.samples.eventfx.si" />

 <bean class="com.soluvas.samples.eventfx.si.Sensor">
  <property name="sensorSyncListener">
   <si:gateway id="sensorSyncListener" service-interface="com.soluvas.samples.eventfx.si.SyncListener"
    default-request-channel="Sensor_SensorEvent_sync" />
  </property>
  <property name="sensorAsyncListener">
   <si:gateway id="sensorAsyncListener" service-interface="com.soluvas.samples.eventfx.si.AsyncListener"
    default-request-channel="Sensor_SensorEvent_async" />
  </property>
 </bean>
 <bean id="display1" class="com.soluvas.samples.eventfx.si.Display">
  <property name="name" value="Sony(sync)" />
 </bean>
 <bean id="display2" class="com.soluvas.samples.eventfx.si.Display">
  <property name="name" value="Samsung(async)" />
 </bean>
 
 <si:channel id="Sensor_SensorEvent_sync" />
 <si:publish-subscribe-channel id="Sensor_SensorEvent_async" />

 <si:service-activator input-channel="Sensor_SensorEvent_sync" ref="display1" method="update" />
 <si:chain input-channel="Sensor_SensorEvent_async">
  <si:delayer default-delay="1200" />
  <si:service-activator ref="display2" method="notify" />
 </si:chain>
 
</beans>

The differences between this Spring Integration version with the Apache Camel version are:
  1. CamelProxyFactoryBean's are replaced with si:gateway beans, which has a more concise syntax.
    Camel 2.2.0 will support similarly concise camel:proxy element that can be used outside camelContext element.
  2. Endpoint URIs are replaced with explicit declaration of Spring Integration Channel beans. Here I use direct channel (si:channel) for sync events and publish-subscribe channel (si:publish-subscribe-channel) for async events.
    I think the the explicit publish-subscribe channel in Spring Integration is very useful. In Apache Camel 2.1 and earlier, the only way to do this is to use multicast. In Apache Camel 2.2, the SEDA component will have a multipleConsumers option that provides a lightweight publish-subscribe mechanism (dynamic multicasting).
  3. I specify the "routing" to Display service-activators using the Spring XML DSL. Apache Camel's Spring DSL feels much more powerful.

The Main Application


The main application, App.java, can't get any simpler:

package com.soluvas.samples.eventfx.si;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
 public static void main(String[] args) {
  new ClassPathXmlApplicationContext("META-INF/spring/*.xml");
 }
}

I'm using Spring Integration 2.0 with Spring Framework 3.0 for this project. For those who are curious, here's the project's Maven pom.xml :

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>com.soluvas.samples</groupId>
 <artifactId>eventfx.si</artifactId>
 <packaging>jar</packaging>
 <version>0.0.1-SNAPSHOT</version>
 <name>eventfx.si</name>
 <url>http://maven.apache.org</url>
 <dependencies>
  <dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>4.7</version>
   <scope>test</scope>
  </dependency>
  <dependency>
   <groupId>org.springframework.integration</groupId>
   <artifactId>spring-integration-core</artifactId>
   <version>2.0.0.M2</version>
  </dependency>
 </dependencies>
 <build>
  <plugins>
   <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
     <source>1.6</source>
     <target>1.6</target>
    </configuration>
   </plugin>
  </plugins>
 </build>
 <repositories>
  <repository>
   <id>spring-milestone</id>
   <name>Spring Portfolio Milestone Repository</name>
   <url>http://s3.amazonaws.com/maven.springframework.org/milestone</url>
  </repository>
 </repositories>
</project>

Event Notification Framework with Spring Integration 2.0 in Action


The app now runs exactly same as the Camel Event Notification example. Here's the application output log:

Jan 5, 2010 3:00:19 AM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@12a54f9: startup date [Tue Jan 05 03:00:19 WIT 2010]; root of context hierarchy
Jan 5, 2010 3:00:19 AM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from file [/home/ceefour/Sandbox/eventfx.si/target/classes/META-INF/spring/eventfx-si.xml]
Jan 5, 2010 3:00:20 AM org.springframework.integration.config.xml.DefaultConfiguringBeanFactoryPostProcessor registerErrorChannelIfNecessary
INFO: No bean named 'errorChannel' has been explicitly defined. Therefore, a default PublishSubscribeChannel will be created.
Jan 5, 2010 3:00:20 AM org.springframework.integration.config.xml.DefaultConfiguringBeanFactoryPostProcessor registerTaskSchedulerIfNecessary
INFO: No bean named 'taskScheduler' has been explicitly defined. Therefore, a default ThreadPoolTaskScheduler will be created.
Jan 5, 2010 3:00:20 AM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@2f0df1: defining beans [sensorSimulator,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.integration.internalDefaultConfiguringBeanFactoryPostProcessor,com.soluvas.samples.eventfx.si.Sensor#0,display1,display2,Sensor_SensorEvent_sync,Sensor_SensorEvent_async,org.springframework.integration.handler.ServiceActivatingHandler#0,org.springframework.integration.config.ConsumerEndpointFactoryBean#0,org.springframework.integration.handler.DelayHandler#e06940,org.springframework.integration.handler.ServiceActivatingHandler#1aae94f,org.springframework.integration.handler.MessageHandlerChain#0,org.springframework.integration.config.ConsumerEndpointFactoryBean#1,nullChannel,errorChannel,org.springframework.integration.handler.LoggingHandler#0,org.springframework.integration.endpoint.EventDrivenConsumer#0,org.springframework.integration.channel.MessagePublishingErrorHandler#0,taskScheduler]; root of factory hierarchy
Jan 5, 2010 3:00:21 AM org.springframework.scheduling.concurrent.ExecutorConfigurationSupport initialize
INFO: Initializing ExecutorService  'taskScheduler'
Jan 5, 2010 3:00:21 AM org.springframework.integration.endpoint.AbstractEndpoint start
INFO: started org.springframework.integration.gateway.SimpleMessagingGateway@1fe571f
Jan 5, 2010 3:00:21 AM org.springframework.integration.endpoint.AbstractEndpoint start
INFO: started si:gateway#1c5fde0
Jan 5, 2010 3:00:21 AM org.springframework.integration.endpoint.AbstractEndpoint start
INFO: started org.springframework.integration.gateway.SimpleMessagingGateway@cf710e
Jan 5, 2010 3:00:21 AM org.springframework.integration.endpoint.AbstractEndpoint start
INFO: started si:gateway#19e8329
Jan 5, 2010 3:00:21 AM com.soluvas.samples.eventfx.si.SensorSimulator initialize
INFO: Sensor simulator initialized.
Jan 5, 2010 3:00:21 AM com.soluvas.samples.eventfx.si.Display initialize
INFO: Display Sony(sync) created.
Jan 5, 2010 3:00:21 AM com.soluvas.samples.eventfx.si.Display initialize
INFO: Display Samsung(async) created.
Jan 5, 2010 3:00:21 AM org.springframework.scheduling.concurrent.ExecutorConfigurationSupport initialize
INFO: Initializing ExecutorService 
Jan 5, 2010 3:00:21 AM org.springframework.integration.endpoint.AbstractEndpoint start
INFO: started org.springframework.integration.config.ConsumerEndpointFactoryBean#0
Jan 5, 2010 3:00:21 AM org.springframework.integration.endpoint.AbstractEndpoint start
INFO: started org.springframework.integration.config.ConsumerEndpointFactoryBean#1
Jan 5, 2010 3:00:21 AM org.springframework.integration.endpoint.AbstractEndpoint start
INFO: started org.springframework.integration.endpoint.EventDrivenConsumer#0
Jan 5, 2010 3:00:23 AM com.soluvas.samples.eventfx.si.Sensor updateText
INFO: updateText: Something happens at 3:00:23 AM
Jan 5, 2010 3:00:23 AM com.soluvas.samples.eventfx.si.Display update
INFO: [Sony(sync)] is updated: 'Something happens at 3:00:23 AM'
Jan 5, 2010 3:00:23 AM com.soluvas.samples.eventfx.si.Sensor fireSensor
INFO: Response: Sony(sync) received Something happens at 3:00:23 AM
Jan 5, 2010 3:00:23 AM org.springframework.integration.endpoint.AbstractEndpoint start
INFO: started org.springframework.integration.endpoint.EventDrivenConsumer@1be2893
Jan 5, 2010 3:00:24 AM com.soluvas.samples.eventfx.si.Display notify
INFO: [Samsung(async)] is notified: 'Something happens at 3:00:23 AM'
Jan 5, 2010 3:00:25 AM com.soluvas.samples.eventfx.si.Sensor updateText
INFO: updateText: Something happens at 3:00:25 AM
Jan 5, 2010 3:00:25 AM com.soluvas.samples.eventfx.si.Display update
INFO: [Sony(sync)] is updated: 'Something happens at 3:00:25 AM'
Jan 5, 2010 3:00:25 AM com.soluvas.samples.eventfx.si.Sensor fireSensor
INFO: Response: Sony(sync) received Something happens at 3:00:25 AM

Which One Is More Powerful?


Technically speaking, I think Apache Camel is more powerful than Spring Integration. And this is not just a "feel", see the Spring XML above to see it for real.

For simpler purposes, both Apache Camel and Spring Integration do their job very well.

For more complex purposes, currently they have their own strengths and weaknesses.

Apache Camel allows you to refer to endpoints either by name ("ref") or by URI (though an endpoints must specify a URI), and creating an endpoint automatically just by referring to its URI. Spring Integration can only refer to a channel by name, and useful (i.e. non-direct) channels need to be created explicitly.

Apache Camel routes are extremely flexible, and can be specified in a variety of DSLs. With Spring Integration it's either basic routing or custom processing. Spring Integration has XML DSL and annotations, that Apache Camel also has.

Spring Integration's concept of publish-subscribe channel is more convenient than Apache Camel's.

Learning More on Enterprise Integration


Spring Integration is all about decoupling messaging between components, which is part of enterprise integration. If you want to learn more about enterprise integration, I heartily recommend the book Enterprise Integration Patterns.