Sunday, March 24, 2013

Creating Spring Integration Service Activator dynamically

My project connects to multiple backend services and we have created a Connector bean for each of the backend services and they all implemented the same interface.  The way we create those Connector beans is using OSGi Config Admin, i.e. we define a configuration for each backend services and Config Admin will create an instance of Connector and registered that to OSGi service registry. 
Let's say the Connector interface has two methods,

Services getAllService()
Service getService(String id)

what we wanted to do is to set a router that would
  1. route to all available Connector for the getAllService call and have an aggregator to aggregate the results from all Connector. 
  2. route to a specific Connector for getService (assuming we can tell which Connector to use by inspecting the id)
The problem is I cannot define service activator or gateway using XML as it's configuration driven.  I've looked into Spring Integration source and determine it's too complicated to try to create a gateway or a service activator programatically.

Inspired by this post, we decided to build our own dynamic service activator.   The router will route to channels with a certain name pattern, <backend service name>.<method name>, e.g. backendA.getAllService.  Then we create a bean, DynamicServiceActivator, that will create channels with matching names and call the Connector bean.  Details about how to create a channel and send a reply is already covered in the above mentioned post, I'm not repeating here.  Since we'll need one instance of DynamicServiceActivator per Connector bean, we get a list of Connector using Gemini's list

<osgi:list interface="Connector">
  <osgi:listener bind-method="addConnector" unbind-method="removeConnector" ref="connectorMgr" />
</osgi:list>

<bean id="dynamicServiceActivator" class="DynamicServiceActivator" scope="prototype"/>

and in the addConnector method, all we have to do is applicationContext.getBean("dynamicServiceActivator") and set the Connector to the activator.

there's one little piece is missing from the above picture.  Since we are creating a channel per method, service activator is suppose to call one method, when we set the connector to the dynamic service activator, it'll have to inspect all method names and create channels for each one of them.  Two ways we can control the method to be exposed, either define in the application context xml, e.g.

<bean id="dynamicServiceActivator" class="DynamicServiceActivator" scope="prototype">
  <property name="methods">
    <map>
      <entry><key>getAllService</key><value>SpEL expression</value></entry>
      <entry><key>getService</key><value>SpEL expression</value></entry>
    </map>
  </property>
</bean>

or annotate the Connector class, so we can find the appropriate method signature to call.