OSGi case study: a modular vert.x
The following case study aims to make the above advantages of OSGi bundles and services concrete. It takes an interesting Java project, vert.x, and shows how it can be embedded in OSGi and take advantage of OSGi's facilities.
Disclaimer: I am not proposing to replace the vert.x container or its module system. This is primarily a case study in the use of OSGi although some of the findings should motivate improvements to vert.x, especially when it is embedded in applications with custom class loaders.
vert.x
vert.x's threading model is interesting as each verticle (or busmod) is bound to a particular thread for its lifetime and so the code of a verticle needn't be concerned about thread safety. A pool of threads is used for dispatching work on verticles and each verticle must avoid blocking or long-running operations so as not to impact server throughput (vert.x provides separate mechanisms for implementing long-running operations efficiently). This is similar to the quasi-reentrant threading model in the CICS transaction processor.1
Of particular interest here is the vert.x module system which has a class loader per verticle and code libraries, known as modules, which are loaded into the class loader of each verticle which uses them. So there is no way to share code between verticles except via the event bus.
vert.x has excellent documentation including a main manual, a java manual (as well as manuals for other language), tutorials, and runnable code examples.
OSGi
Embedding vert.x in OSGi
Converting vert.x JARs to OSGi Bundles
I used the bundlor tool, although other tools such as bnd would work equally well. Bundlor takes a template and then analyses the bytecode of the JAR to produce a new JAR with appropriate OSGi manifest headers. Please refer to the SpringSource Bundlor documentation for further information about bundlor for now as the Eclipse Virgo Bundlor documentation is not published at the time of writing even though the bundlor project has transferred to Eclipse.org.
The template for the vert.x core JAR is as follows:
Bundle-ManifestVersion: 2 Bundle-SymbolicName: org.vertx.core Bundle-Version: 1.0.0.final Bundle-Name: vert.x Core Import-Template: org.jboss.netty.*;version="[3.4.2.Final,4.0)", org.codehaus.jackson.*;version="[1.9.4,2.0)", com.hazelcast.*;version="[2.0.2,3.0)";resolution:=optional, groovy.*;resolution:=optional;version=0, org.codehaus.groovy.*;resolution:=optional;version=0, javax.net.ssl;resolution:=optional;version=0, org.apache.log4j;resolution:=optional;version=0, org.slf4j;resolution:=optional;version=0 Export-Template: *;version="1.0.0.final"
(The template and all the other parts of this case study are available on github.)
What this does is define the valid range of versions for packages that the JAR depends on (the range "0" represents the version range of 0 or greater), whether those packages are optional or mandatory, and what version the JARs own packages should be exported at. It also gives the bundle a symbolic name (used to identify the bundle), a version, and a (descriptive) name. Armed with this information, OSGi then wires together the dependencies of bundles by delegating class loads and resource lookups between bundle class loaders.
Thankfully the netty networking JAR and jackson JSON JARs which the vert.x core JAR depends on ship with valid OSGi manifests.
As a sniff test that the manifest was valid, I tried deploying the vert.x core bundle in the Virgo kernel. This was simply a matter of placing the vert.x core bundle in the pickup directory and its dependencies in the repository/usr directory and then starting the kernel. The following console messages showed the vert.x core bundle was installed and resolved successfully:
<hd0001i> Hot deployer processing 'INITIAL' event for file 'vert.x-core-1.0.0.final.jar'. <de0000i> Installing bundle 'org.vertx.core' version '1.0.0.final'. <de0001i> Installed bundle 'org.vertx.core' version '1.0.0.final'. <de0004i> Starting bundle 'org.vertx.core' version '1.0.0.final'. <de0005i> Started bundle 'org.vertx.core' version '1.0.0.final'.Using the Virgo shell, I then checked the wiring of the bundles:
osgi> ss "Framework is launched." id State Bundle 0 ACTIVE org.eclipse.osgi_3.7.1.R37x_v20110808-1106 ... 89 ACTIVE org.vertx.core_1.0.0.final 90 ACTIVE jackson-core-asl_1.9.4 91 ACTIVE jackson-mapper-asl_1.9.4 92 ACTIVE org.jboss.netty_3.4.2.Final osgi> bundle 89 org.vertx.core_1.0.0.final [89] ... Exported packages ... org.vertx.java.core; version="1.0.0.final"[exported] org.vertx.java.core.buffer; version="1.0.0.final"[exported] ... Imported packages org.jboss.netty.util; version="3.4.2.Final"<org.jboss.netty_3.4.2.final [92]> ... org.codehaus.jackson.map; version="1.9.4"<jackson-mapper-asl_1.9.4 [91]> ...I also converted the vert.x platform JAR to an OSGi bundle in similar fashion as it was needed later.
Modularising Verticles
public class ServerExample extends Verticle { public void start() { vertx.createHttpServer().requestHandler(new Handler<httpserverrequest>() { public void handle(HttpServerRequest req) { ... } }).listen(8080); } }
Let's look at the modular verticle bundle. Its code consists of a single HttpServerRequestHandler class:2
public final class HttpServerRequestHandler implements Handler<httpserverrequest> { public void handle(HttpServerRequest req) { ... } }
<?xml version="1.0" encoding="UTF-8"?> <blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"> <service interface="org.vertx.java.core.Handler" ref="handler"> <service-properties> <entry key="type" value="HttpServerRequestHandler"> <entry key="port" value="8090"> </service-properties> </service> <bean class="org.vertx.osgi.sample.basic.HttpServerRequestHandler" id="handler"/> </blueprint>This metadata declares that a HTTP server should be created (via the type service property), the handler registered with it, and the server set listening on port 8090 (via the port service property). This all happens courtesy of the whiteboard pattern when the org.vertx.osgi bundle is running as we'll see below.
Notice that the modular verticle depends only on the Handler and HttpServerRequest classes whereas the original verticle also depends on the Vertx, HttpServer, and Verticle classes. This also makes things quite a bit simpler for those of us who like unit testing (in addition to in-container testing) as fewer mocks or stubs are required.
So what do we now have? Two bundles to add to the bundles we installed earlier: an org.vertx.osgi bundle which encapsulates the boilerplate code and an application bundle representing a modular verticle. We also need a Blueprint service implementation -- as of Virgo 3.5, a Blueprint implementation is built in to the Virgo kernel. The following interaction diagram shows one possible sequence of events:
In OSGi, each bundle has its own lifecycle and in general bundles are designed so that they will function correctly regardless of the order in which they is started relative to other bundles. In the above example the assumed start order is: blueprint service, org.vertx.osgi bundle, modular verticle bundle. However, the org.vertx.osgi bundle could start after the modular verticle bundle and the end result will be the same: a server will be created and the modular verticle bundle's handler registered with the server and the server set listening. If the blueprint service is started after the org.vertx.osgi and modular verticle bundles, then the org.vertx.osgi bundle won't detect the modular verticle bundle's handler service appear in the service registry until the blueprint service has started, but then the end result will again be the same.
The github project contains the source for some sample modular verticles: a basic HTTP vertical (which runs on port 8090) and a sockjs verticle (which runs on port 8091). The org.vertx.osgi bundle needed more code to support sockjs and the modular sockjs verticle needed to provide a sockjs handler in addition to a HTTP handler.
Modularising BusMods
public class MongoPersistor extends BusModBase implements Handler<message<jsonobject>> { private String address; private String host; private int port; private String dbName; private Mongo mongo; private DB db; public void start() { super.start(); address = getOptionalStringConfig("address", "vertx.mongopersistor"); host = getOptionalStringConfig("host", "localhost"); port = getOptionalIntConfig("port", 27017); dbName = getOptionalStringConfig("db_name", "default_db"); try { mongo = new Mongo(host, port); db = mongo.getDB(dbName); eb.registerHandler(address, this); } catch (UnknownHostException e) { logger.error("Failed to connect to mongo server", e); } } public void stop() { mongo.close(); } public void handle(Message<jsonobject> message) { ... } }
Here's the blueprint.xml:
<?xml version="1.0" encoding="UTF-8"?> <blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"> <service ref="handler" interface="org.vertx.java.core.Handler"> <service-properties> <entry key="type" value="EventBusHandler"/> <entry key="address" value="vertx.mongopersistor"/> </service-properties> </service> <bean id="handler" class="org.vertx.osgi.mod.mongo.MongoPersistor" destroy-method="stop"> <argument type="java.lang.String"><value>localhost</value></argument> <argument type="int"><value>27017</value></argument> <argument type="java.lang.String"><value>default_db</value></argument> </bean> </blueprint>Notice that the boilerplate configuration consists of the handler type and event bus address. The other configuration (host, port, and database name) is specific to the MongoDB persistor.
Here's the modular MongoDB busmod code:
public class MongoPersistor extends BusModBase implements Handler<Message<JsonObject>> { private final String host; private final int port; private final String dbName; private final Mongo mongo; private final DB db; public MongoPersistor(String host, int port, String dbName) throws UnknownHostException, MongoException { this.host = host; this.port = port; this.dbName = dbName; this.mongo = new Mongo(host, port); this.db = this.mongo.getDB(dbName); } public void stop() { mongo.close(); } public void handle(Message<JsonObject> message) { ... } }The code still extends BusModBase simply because BusModBase provides several convenient helper methods. Again the resultant code is simpler and easier to unit test than the non-modular equivalent.
Modularising Event Bus Clients
The blueprint.xml for the modular event bus client is as follows:
<?xml version="1.0" encoding="UTF-8"?> <blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"> <reference id="eventBus" interface="org.vertx.java.core.eventbus.EventBus"/> <bean class="org.vertx.osgi.sample.mongo.MongoClient"> <argument ref="eventBus"/> <argument type="java.lang.String"> <value>vertx.mongopersistor</value> </argument> </bean> </blueprint>Then the modular event bus client code is straightforward:
public final class MongoClient { public MongoClient(EventBus eventBus, String address) { JsonObject msg = ... eventBus.send(address, msg, new Handler<Message<JsonObject>>(){...}); } }
Taking it for a Spin
1. I've made all the necessary OSGi bundles available in the bundles directory in git. You can grab them either by cloning the git repository:git clone git://github.com/glyn/vert.x.osgi.git
or by downloading a zip of the git repo.
2. vert.x requires Java 7, so set up a terminal shell to use Java 7. Ensure the JAVA_HOME environment variable is set correctly. (If you can't get Java 7 right now, you'll see some errors when the bundles are deployed to OSGi and you won't be able to run the samples in steps 8 and 9.)
3. If you are an OSGi user, simply install and start the bundles in your favourite OSGi framework or container and skip to step 8. If not, then use the copy of the Virgo kernel in the git repository as follows.
4. Change directory to the virgo-kernel-... directory in your local copy of the git repo.
5. On UNIX, issue:
bin/startup.sh -clean
or on Windows, issue:
bin\startup.bat -clean
6. The Virgo kernel should start and deploy the various bundles in its pickup directory:
- org.vertx.osgi bundle (org.vertx.osgi-0.0.1.jar)
- HTTP sample modular verticle (org.vertx.osgi.sample.basic-1.0.0.jar)
- SockJS sample modular verticle (org.vertx.osgi.sample.sockjs-1.0.0.jar)
- MongoDB persistor sample modular busmod (org.vertx.osgi.mods.mongo-1.0.0.jar)
... 89 ACTIVE org.vertx.osgi_0.0.1 90 ACTIVE jackson-core-asl_1.9.4 91 ACTIVE jackson-mapper-asl_1.9.4 92 ACTIVE org.jboss.netty_3.4.2.Final 93 ACTIVE org.vertx.core_1.0.0.final 94 ACTIVE org.vertx.osgi.mods.mongo_1.0.0 95 ACTIVE com.mongodb_2.7.2 96 ACTIVE org.vertx.platform_1.0.0.final 97 ACTIVE org.vertx.osgi.sample.basic_1.0.0 98 ACTIVE org.vertx.osgi.sample.sockjs_1.0.0and of the lb command (which includes the more descriptive Bundle-Name headers):
... 89|Active | 4|vert.x OSGi Integration (0.0.1) 90|Active | 4|Jackson JSON processor (1.9.4) 91|Active | 4|Data mapper for Jackson JSON processor (1.9.4) 92|Active | 4|The Netty Project (3.4.2.Final) 93|Active | 4|vert.x Core (1.0.0.final) 94|Active | 4|MongoDB BusMod (1.0.0) 95|Active | 4|MongoDB (2.7.2) 96|Active | 4|vert.x Platform (1.0.0.final) 97|Active | 4|Sample Basic HTTP Verticle (1.0.0) 98|Active | 4|Sample SockJS Verticle (1.0.0)
9. If you want to try the (headless) MongoDB event bus client, download MondoDB and start it locally on its default port and then copy org.vertx.osgi.sample.mongo-1.0.0.jar from the bundles directory to Virgo's pickup directory. As soon as this bundle starts, it will send a message to the event bus and drive the MongoDB persistor to update the database. If you don't want to use MongoDB to check that an update was made, take a look in Virgo's logs (in serviceability/logs/log.log) to see some System.out lines like the following that confirmed something happened:
System.out Sending message: {action=save, document={x=y}, collection=vertx.osgi} ... System.out Message sent ... System.out Message response {_id=95..., status=ok}
OSGi and vert.x Modularity
This is quite different from the vert.x module system in which any module (other than a busmod) which a verticle depends on is loaded into the same class loader as the verticle.
The advantages of the OSGi module system are that a single copy of each module is installed in the system and is visible to and may be managed by tools such as the Virgo shell. It also minimises footprint.
The advantages of the vert.x module system are that there is no sharing of modules between verticles so a badly-written module could not inadvertently or deliberately leak information between independent verticles. Also, there is a separate copy of each (non-busmod) module for each verticle that uses it and so the module can be written without worrying about thread safety as each copy will only be executed on its verticle's thread. OSGi users may, however, be happy to require reusable modules to be thread-safe and manage any mutable static data carefully to avoid leakage between threads.
Replacing the Container?
Conclusions
The vert.x module system, although not providing isolation between modules, does neatly provide isolation between applications (comprising verticles and their modules) and it enables modules to be written without paying attention to thread safety.
vert.x could follow the example of netty, jackson, and MongoDB JARs and include OSGi manifests in its core and platform JARs to avoid OSGi users having to convert these JARs to OSGi bundles. I will leave this to someone else to propose as I cannot gauge the demand for using vert.x inside OSGi.
Running vert.x in OSGi addresses some outstanding vert.x requirements such as how to automate in-container tests (OSGi has a number of solutions including Pax Exam while Virgo has a integration test framework) and how to develop verticles and deploy them to vert.x under control of the IDE (see the Virgo IDE tooling guide). Virgo also provides numerous ancillary benefits including the admin shell for inspecting and managing bundles and verticles, sophisticated diagnostics, and much more (see the Virgo white paper for details).
Finally, running vert.x on the Virgo kernel is a further validation that the kernel is suitable for building custom server runtimes since now we have vert.x in addition to Tomcat, Jetty, and one or two custom servers running on the kernel.
- I worked in the CICS development team in my IBM days. A colleague at SpringSource gave me a "CICS Does That!" T-shirt soon after we'd started working together. Old habits die hard.
- The modular vertical currently needs to intercept vert.x's resource lookup logic so that files in the bundle can easily be served. It would be much better for this common code to move to the org.vertx.osgi bundle, but this requires vert.x issue 161 to be implemented first.