Distributing Razor Events Using Pub/Sub (3/N)
So yeah, it seems that all ESB systems are complicated to some degree. Having struck out with WSo2 I did a brief survey of other FOSS ESB offerings and decided to take a stab at Apache ServiceMix. So far, so good.
ServiceMix is a bundle of a few different Apache projects:
- Karaf, a Java runtime that houses the other bits.
- ActiveMQ, the reliable messaging service.
- Camel, which provides the routing functionality.
- CXF, which is used for building web services.
- Unified package: The router and broker components are housed together in a single runtime. With WSo2 I had to jump back-and-forth between two different interfaces and two sets of logs.
- Lightweight/modular: Has a lot of available components, but most of them are (sensibly) disabled by default. This leads to quicker startup and a smaller footprint.
- Command-line oriented: Provides a text shell that lets you monitor and configure most aspects of the system. This compares favorably with the web interfaces used by WSo2.
- Robust documentation: There's good documentation available online and there are a number of decent books on the components which make up ServiceMix. Additionally, I get the impression based on Google searches that the user community is more robust.
So let's try the same scenario, getting the bare skeleton of a pub-sub system set up. First off, download ServiceMix and start it up:
wgt https://www-us.apache.org/dist/servicemix/servicemix-7/7.0.1/apache-servicemix-7.0.1.zip unzip apache-servicemix-7.0.1.zip apache-servicemix-7.0.1/bin/servicemixYou should get a startup message and then be dumped into the Karaf console:
karaf@root>
Alright then, let's get started. There should be an ActiveMQ instance lurking about, and https://activemq.apache.org/osgi-integration.html tells me I can see its status via the bstat command:
karaf@root>bstat BrokerName = amq-broker TotalEnqueueCount = 1 TotalDequeueCount = 0 TotalMessageCount = 0 TotalConsumerCount = 0 Uptime = 36.659 seconds Name = KahaDBPersistenceAdapter[xxxxx] connectorName = openwireGreat, so the broker instance is operational. Unlike WSo2, its not strictly necessary to pre-configure pub-sub topics as they'll be auto-created as necessary. IMHO that's bad operational hygiene, as its likely to lead to an accumulation of misspelled and obsolete topics, so I'm going to pre-define a topic by editing ./etc/activemq.xml as described in https://svn.apache.org/repos/infra/websites/production/activemq/content/configure-startup-destinations.html:
<destinations> <topic physicalName="RAZOR" /> </destinations>Restart ServiceMix, and now bstat shows:
karaf@root>bstat BrokerName = amq-broker ... Name = RAZOR destinationName = RAZOR destinationType = Topic EnqueueCount = 0 DequeueCount = 0 ConsumerCount = 0 DispatchCount = 0 ...Looking good.
We've got a message broker, we've got a pub-sub topic, now let's set up some stub pub-sub consumers. At this point we're going to stop working with the ActiveMQ portion of the system and start working with the Camel portion. Here's the XML that sets up two consumers which do nothing but log, based on the example from https://servicemix.apache.org/docs/7.x/quickstart/camel.html:
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd"> <camelContext xmlns="http://camel.apache.org/schema/blueprint"> <route> <from uri="activemq:topic:RAZOR"/> <log message="RAZOR topic consumer #1"/> <to uri="mock:topicsink" /> </route> <route> <from uri="activemq:topic:RAZOR"/> <log message="RAZOR topic consumer #2"/> <to uri="mock:topicsink" /> </route> </camelContext> </blueprint>The blueprint and camelContext tags are boilerplate; all the action happens in the route blocks. Here's how those are interpreted:
- Create a route that consumes messages from the RAZOR topic of the ActiveMQ instance, writes a log message "RAZOR topic consumer #1", and then sends the message to the mock (i.e. fake test fixture) destination topicsink.
- Rinse and repeat, this time logging the message "RAZOR topic consumer #2".
karaf@root>bstat ... Name = RAZOR destinationName = RAZOR destinationType = Topic EnqueueCount = 0 DequeueCount = 0 ConsumerCount = 2 DispatchCount = 0 ...
Let's test it out. The ServiceMix console provides the activemq:producer command for sending messages to the ActiveMQ instance:
karaf@root>activemq:producer --destination topic://RAZOR --message 'test' --user smx --password smx --messageCount 1 karaf@root>log:display -n 2 2019-08-20 14:03:03,627 | INFO | sConsumer[RAZOR] | route3 | 43 - org.apache.camel.camel-core - 2.16.5 | RAZOR topic consumer #1 2019-08-20 14:03:03,627 | INFO | sConsumer[RAZOR] | route4 | 43 - org.apache.camel.camel-core - 2.16.5 | RAZOR topic consumer #2The messages show up in the log as expected. A couple of comments:
- The user name and password for connecting the ActiveMQ are defined by activemq.jms.user and activemq.jms.password in ./etc/system.properties.
- The log:display command is pretty nifty; it lets you view the system log from the shell instead of hunting it down in the file system.
So we've verified that the consumers are working. The last thing I want to do in this post is set up a simple REST interface for receiving messages destined for the RAZOR topic. Things get a little confusing here, as ServiceMix has a plethora of options for creating RESTful services:
- CXF: A full-featured service framework.
- REST Swagger: Creates RESTful services using Swagger specifications.
- REST: Allows definition of REST endpoints and provides REST transport for other components.
- RESTlet: Yet another REST endpoint implementation.
Additional complexity is added by the fact that some of the options above can be provided by one of several software components. For example, according to the docs, REST consumer functionality is provided by any of: camel-coap, camel-netty-http, camel-jetty, camel-restlet, camel-servlet, camel-spark-rest, or camel-undertow. One of these needs to be installed and enabled in the ServiceMix runtime before the REST directive can be used. I chose camel-restlet for no particular reason:
karaf@root>feature:install camel-restlet karaf@root>feature:list | grep camel-restlet camel-restlet | 2.16.5 | x | Started | camel-2.16.5 |
And now, the augmented XML:
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd"> <camelContext xmlns="http://camel.apache.org/schema/blueprint"> <restConfiguration bindingMode="auto" component="restlet" port="8080" /> <route> <from uri="rest:post:razor" /> <inOnly uri="activemq:topic:RAZOR" /> </route> <route> <from uri="activemq:topic:RAZOR"/> <log message="RAZOR topic consumer #1"/> <to uri="mock:topicsink" /> </route> <route> <from uri="activemq:topic:RAZOR"/> <log message="RAZOR topic consumer #2"/> <to uri="mock:topicsink" /> </route> </camelContext> </blueprint>Here's how this works:
- The restConfiguration tag indicates which software component (restlet, which we just installed) will be providing REST endpoint services and that the REST service should be provided on port 8080.
- ServiceMix should listen for POST requests to /razor and route them to the RAZOR ActiveMQ topic. The use of inOnly in that routing rule indicates that no reply should be expected.
$ netstat -an | grep -i listen | grep 8080 tcp46 0 0 *.8080 *.* LISTEN
Alright, lets send a message!
$ curl --data 'message' http://localhost:8080/razorand
karaf@root>log:display -n 2 2019-08-21 14:28:52,720 | INFO | sConsumer[RAZOR] | route3 | 43 - org.apache.camel.camel-core - 2.16.5 | RAZOR topic consumer #2 2019-08-21 14:28:52,720 | INFO | sConsumer[RAZOR] | route2 | 43 - org.apache.camel.camel-core - 2.16.5 | RAZOR topic consumer #1Boom!
Having reached the point where we can submit a message via REST we'll call it a day. Observations so far:
- ServiceMix isn't exactly simple, but it's less complicated than WSo2.
- I'm particularly enamored of Camel's XML DSL; it seems very well designed for succinctly setting up routing rules. Putting the basic pub-sub skeleton together was a matter of a few route blocks. Compare this with WSo2, which required me to set up 3 different proxy services to accomplish the same effect.