Wednesday, August 21, 2019

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.
I'm finding it to be significantly easier to deal with than WSo2. That's not entirely the fault of WSo2, since a lot of the subject matter background that I had to slog through for WSo2 ports directly to ServiceMix. Had I tried ServiceMix first I might have voiced the same complaint, but in reverse. That said, however, ServiceMix still feels like an easier system to work with for a number of reasons:
  • 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/servicemix
You 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 = openwire
Great, 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".
Create a file with the above content and then copy into the deploy directory. ServiceMix should notice the new file in a few seconds and then configure the routes. If all has gone well then there should now be two registered consumers for the RAZOR topic:
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 #2
The 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.
A non-trivial amount of doc-reading led me to conclude that the REST component was best-suited to my needs, since all I really want is for ServiceMix to listen for inbound HTTP POSTs and then pass the payload on to ActiveMQ.

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.
Copy the updated XML into the deploy directory. Once ServiceMix has picked up the new configuration there should now be something listening on port 8080:
$ netstat -an | grep -i listen | grep 8080
tcp46      0      0  *.8080                 *.*                    LISTEN

Alright, lets send a message!

$ curl --data 'message' http://localhost:8080/razor
and
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 #1
Boom!

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.
When we pick up we'll do the integration work needed to catch Razor events and send them to ServiceMix.

0 Comments:

Post a Comment

<< Home

Blog Information Profile for gg00