I recently had a client with a use case that I thought would be interesting to share (and they were happy for me to talk about – no names, industry changed). Sample code and full instructions for the solution available as always at FuseByExample on Github.
Imagine a system integration where the core logic is static, but the systems that participate in a particular process change over time. Take as an example, a travel booking system that accepts orders for buying flights. How do you go about adding new integration logic for a particular capability such as the booking of a flight with a new airline via its system? On top of this:
- without changing any of the core logic around the main business process (the booking of a flight also includes taking payment)
- with no application downtime (hot deployment)
- enabling other development teams to define integrations for new airlines
Conceptually, these requirements could be satisfied via an application-level “plugin” solution. Your core flight booking application forms a platform alongside which you deploy capabilities specific to individual airlines. This may seem like a complicated set of requirements, but there is a set of tools that you can use to enable exactly this type of application.
Using Camel provides us with a way that to easily partition integration logic into a core process (flight bookings) and sub-processes (booking a ticket with a particular airline), and dynamically route to the right sub-process depending on the booking request. Routes can be connected within the one container by using the ServiceMix NMR to pass messages beteen bundles.
Deploying logic that is specific to an individual airline as an OSGi component enables the separation of code away from the core process, and provides the required dynamicity (and allows others to write that logic). The trick is in putting it all together.
OSGi bundles can be thought of as mini-applications. Basing a bundle on SpringDM/Blueprints (essentially defining a Spring-like config in a known location), we can embed a Camel context inside it and have routing logic start up when that bundle is deployed. This is a good candidate for our airline-specific booking code.
We then need to somehow advise the main application bundle that the new process is on-line and ready to do business by having messages routed to it. To achieve this part, we use of the OSGi service registry. To those unfamiliar with it, the registry acts as a conceptual whiteboard inside an OSGi container. Our plugin bundles can register beans inside the registry as implementors of an interface. The core application that wishes to use these services looks up the registry to get a handle on the implementations.
To advertise the availability of a sub-process and provide the name of the Camel endpoint that accepts bookings for a particular airline, we use an interface that indicates to the main application that a plugin bundle is available to take bookings. This is placed in its own bundle such that it could be implemented in the airline-specific bundles, and used in the main booking process.
public interface BookingProcessor { public String getAirlineCode(); public String getBookingRouteUri(); }
Each airline bundle defines its own implementation that returns the airline code that it accepts booking messages for, and the URI for the endpoint of the Camel route that it would be listening for messages on.
This object is then registered with the OSGi service registry:
<osgi:service ref="germanAirlinePlugin" interface="com.fusesource.examples.booking.spi.BookingProcessor" />
The main process bundle is then able to get a dynamic proxy to a set of BookingProcessors that may come and go:
<osgi:set id="bookingProcessors" interface="com.fusesource.examples.booking.spi.BookingProcessor" cardinality="0..N"/>
This set can then be injected into a bean (BookingProcessorRegistry) that makes decisions such as:
- Is this airline currently supported by the system?
- What is the route that can be invoked to process this booking?
The Camel routing logic can then be really simple:
<route id="placeBooking"> <from uri="jetty:http://0.0.0.0:9191/booking" /> <transform> <simple>${headers.flightNumber}</simple> </transform> <choice> <when> <method bean="bookingProcessorRegistry" method="isAirlineSupported"/> <recipientList stopOnException="true" strategyRef="bookingResponseAggregationStrategy"> <constant>direct:takePayment, direct:placeBookingWithAirline</constant> </recipientList> </when> <otherwise> <transform> <simple>Unable to book flight for ${body} - unsupported airline</simple> </transform> </otherwise> </choice> </route> <route id="placeBookingWithAirline"> <from uri="direct:placeBookingWithAirline" /> <!-- work out who to send the message to --> <setHeader headerName="bookingProcessor"> <method bean="bookingProcessorRegistry" method="getBookingProcessorUri"/> </setHeader> <log message="Calling out to ${headers.bookingProcessor}"/> <recipientList> <header>bookingProcessor</header> </recipientList> </route>
So to make a new airline available for bookings, you:
- create a new OSGi bundle
- register a BookingProcessor in the service registry that indicates the route that processes these bookings
- write the integration logic in a route that listens on that endpoint
- build the bundle and drop it into Servicemix alongside the main process.
Voila! An application-specific plugin system. You can then use the Karaf web console as a mechanism to make the bundle logic available via REST to a single container, or if you want to distribute it across a cluster – Fuse Fabric via Fuse ESB Enterprise.
Comments
4 responses to “System Integrations as Plugins using Camel and ServiceMix”
[…] System Integrations as Plugins using Camel and ServiceMix […]
Great jib !
I really think OSGi/blueprint should be used a replacement of JEE programming model.
I think you should complete your sample with database access (using Derby for instance) and JMS messaging (with activeMQ for instance.
I personnally work on the OSGI-fication of Atomikos JTA TM (http://blog.atomikos.com/2012/05/release-38-is-now-available/).
Best regards
Thanks Pascal. A SQL (actually MyBatis) and AMQ example is the next sample that I’m going to put together. Maybe even with XA 😉
Jakub,
You point out you can use NMR for communicating between bundles. With the JBI NMR, I think you can enable persistent messaging since it’s backed by ActiveMQ. Can you say a few words about how to accomplish the same thing with the NMR deployed as an OSGI bundle? Can it be configured to be backed by ActiveMQ also?