JMS Courier Technology
On this page we're going to delve a bit deeper into the technologies that underpin JMS Courier.
Let's take a look at the architecture of an integrated messaging solution. The image below shows three applications, one written in Java, one in C++, and one in .NET. All three use the same JMS implementation to communicate over a JMS message bus. This is to mean, all three can send and receive messages from topics and queues, both synchronously and asynchronously.
The first thing we want to point out is that the Java application (on the left), talks to the vendor's JMS implementation through the standardized JMS API. When done correctly, all you need to do to switch to a different JMS implementation is to change some runtime configuration settings.
The Picture on the C++ Side
Now let's take a look at the C++ client application. Please note that all the components within it run in the same process, implying a secure, tight, highly performant, combined solution.
When compared with the Java client, you have the identical picture at the bottom two layers of the stack. This means that—as far as the JMS message bus is concerned—it appears to be talking to a Java client application.
At the top you have C++ application code instead of Java application code. Of course that's the whole point of the integration: your C++ developers want to write C++ code to communicate with JMS.
In the middle, JMS Courier provides a high-quality C++ API to the C++ side and a highly reliable and performant cross-language gateway to the Java side.
You probably want to know what this cross-language gateway looks like at runtime. The diagram below illustrates this.
We use the Java Native Interface (JNI) to cross the language barrier. JNI has a poor reputation for performance and reliability, which is entirely undeserved as far as its capabilities are concerned and totally deserved as far as its usability is concerned. If you have to write JNI code by hand you invariably end up with crashing and poorly performing applications, both usually due to memory leaks. Yet the Java Runtime integrates with the underlying operating system through JNI, which tells you that it must be possible to use JNI effectively.
We use JNI through a carefully crafted set of utility APIs that itself is used by generated code. You never deal with JNI directly and that is the key to JMS Courier's speed and reliability.
Let's take a look at some C++ code that uses the JMS Courier C++ proxy types.
InitialContext ictx(_use_java_ctor); Topic topic = Topic::dyna_cast( ictx.lookup( "topic/topic0" )); TopicConnectionFactory tcf = TopicConnectionFactory::dyna_cast( ictx.lookup( "topic/connectionFactory" ) ); TopicConnection tc = tcf.createTopicConnection(); TopicSession ts = tc.createTopicSession( false, Session::AUTO_ACKNOWLEDGE ); TopicPublisher tp = ts.createPublisher( topic ); tp.setDeliveryMode( DeliveryMode::NON_PERSISTENT ); TextMessage tm = ts.createTextMessage(); tm.setText( "Hello World!" ); tp.publish( tm ); tc.close();
You will probably notice a few unexpected details in the code above. There is a strange constructor argument called
_use_java_ctor and we're using a function called
dyna_cast() in a few places. Other than that,
this code should strike you as looking remarkably similar to the corresponding Java code.
Your C++ developers can be productive JMS developers with a few hours of training.
The Picture on the .NET Side
Everything we said about the C++ client holds true for the .NET client as well, of course with your favorite .NET language taking the place of C++. The stack is one layer higher because the JMS Courier .NET Proxies talk to a managed .NET runtime assembly, which in turn talks to the C++ runtime library from our C++ use case.
This results in the following runtime illustration for the .NET side:
The hand-off from .NET to C++ is done through P/Invoke, .NET's native escape hatch from the world of managed code.
Now let's look at some .NET code that uses the JMS Courier .NET proxy types.
InitialContext ictx = new InitialContext(); Topic topic = TopicImpl.From( ictx.Lookup( "topic/topic0" )); TopicConnectionFactory tcf = TopicConnectionFactoryImpl.From( ictx.Lookup( "topic/connectionFactory" ) ); TopicConnection tc = tcf.CreateTopicConnection(); TopicSession ts = tc.CreateTopicSession( false, SessionImpl.AUTO_ACKNOWLEDGE ); TopicPublisher tp = ts.CreatePublisher( topic ); tp.SetDeliveryMode( DeliveryModeImpl.NON_PERSISTENT ); TextMessage tm = ts.CreateTextMessage(); tm.SetText( "Hello World!" ); tp.Publish( tm ); tc.Close();
As with the C++ code, there are a few surprises here, namely the
Impl types we use to access static interface
members and the
From() we use to cast. Other than that, the code should look incredibly familiar to anyone
who has used JMS in Java.
Your .NET developers can be productive JMS users in a matter of hours.
At runtime, all these calls happen blazingly fast when compared to alternative integration approaches. JNI has a poor reputation when it comes to reliability and performance. Yet the fact remains that it is the fastest way of crossing the language boundary by far. By using a proven and tested runtime library and generated code to invoke the Java Native Interface we also make sure to obey best practices and avoid many of the performance traps and all of the memory leaks and crashes that plague hand-written JNI code.
As you are talking to an enterprise messaging API, you will hardly see any performance degradation at all in the integrated application. The cross-language call overhead will be negligible in comparison to the time it takes to deliver or receive the message.
It goes without saying that your JMS vendor can implement a hand-optimized native gateway that is faster and has lower latency than what we can do through JNI. If you need the utmost of speed you might not have a choice. Just be aware that you will be facing vendor lock-in and the proprietary native gateway will probably not be feature-complete. Nothing comes without a price.
The in-process nature of the integration also means that you do not have to worry about new security vulnerabilities due to the integration. There are no ports to be opened, firewalls to be configured, or certificates to be installed just to secure the integration solution. As long as you take a little bit of care with your runtime configuration, your threat profile is essentially the superset of a pure native application's and a pure Java JMS application's threat profiles.
Mature Configuration Framework
The Java Runtime Environment has many options—classpath is just the best known one—and you might have to use many of them to configure your integrated application's JVM. The JunC++ion runtime offers a mature configuration API that allows you to hardcode settings in your application, use configuration files, pick up settings from the environment, or even from shared libraries when they get loaded.