On Monday, I managed to complete the release of two new gems and two gem updates, all part of the RJack project hosted on RubyForge. As this is my first post on RJack, here is a an introduction without quoting the linked project summary, and some details on jar in gem packaging.
RJack provides gem packaging of various broadly used Java components for use with JRuby. Included in this release set are upstream updates to the slf4j and logback logging system, and the new gems jetty and jetty-jsp, which package the Jetty Web Server. A guiding design principal is that each of these gems may be used simply and solely to deliver java package dependencies to a consuming ruby/java application. However, three of the four gems in the current set also provide either façades for setup or adapters for direct use from ruby code. For these inclusions, care is given to avoid incurring any additional external dependencies.
The RubyGems package management system offers several advantages for working with server application components in both development and production environments, including a straight forward command line interface, multiple remote and local gem repositories, versioned dependency management, support for multiple versions of the same component, and support for installing command-line accessable executables. While in common use for pure and C-extended ruby components, JRuby provides an opportunity to similarly utilize gems for java-extended ruby or even near-pure java components. The nearest alternative from the java comunity would be Maven and its derivatives, but these are far more appropriate for handling java dependencies at compile time than for production deployment.
Once the logical decision to deploy gems is made, the first order of business is determining how to divide the various components and their java jar dependencies into gems for maximum benefit including reuse, reliability, and ease/safety of upgrade with independent release cycles. Java components are ultimately loaded by searching through an order list of jars or directories, the “classpath”. If multiple versions of the same java package are found on the classpath, the first instance wins. Problems arise if two utilized components include incompatible versions of the same package. It is therefore advantageous to break common java dependencies out into individual gems and utilize the version management offered by the rubygems system.
As a prime example, the SLF4J system is predicated on the application developer selecting a final logging destination by installing the appropriate logging adapter in the classpath. If however various gems with some java dependencies each in turn include their own versions of these or other logging system jars, then this logging output flexibility is lost. As SLF4J is now the best available logging interface for Java, I’ve released an independent slf4j gem, for reuse and in the hope of encouraging other java gem packagers to use the slf4j gem to resolve any java logging dependencies, in lieu of embedding alternative logging components in their gems.
Since Maven manages dependencies at the jar level, a one-to-one mapping would suggest a gem per jar. While some discussion has taken place on automating direct jar to gem packing, a significant offering has yet to be published. I’m also not convinced this is the right mapping. Consider that in ruby, the
require method is included in normal program flow, and its common to use multiple
require calls to selectively bring in different parts of a single gem. JRuby extends
require to support adding a named jar file to the classpath of its custom class loader. Its therefore quite reasonable for gems to map directly to a java project which provides more than one jar. A clear example of this is the RJack slf4j gem. See the SLF4J module documentation for
require mappings to eight additional input/output adapter jars beyond the
slf4j-api-version.jar loaded with the base
require 'slf4j'. There will be other cases where a single jar is best packaged as a gem, but its clearly not necessary to do so when a project releases multiple jar’s in version lock-step.
A counter argument for finer grain gems is the case where, as with the jetty-jsp gem, large jars are included and some of these are commonly not needed. The jetty gem contains the core server components and servlet-api (4 gems) at 0.7M where as the jetty-jsp gem adds JSP support (4 more gems) for an additional 5.2M. There are also cases where external project jar dependencies are added to a gem. The argument for this would be that it is unlikely that a particular dependency will be used in another context. Of course this could be a mistake, but its one that can be fixed later with a patch release, breaking the dependency out into its own gem.
Finally its worth noting that these gems defer to the upstream java project version numbers, adding a single gem package release number to the end of the upstream version. This puts emphasis on version compatibility in upstream project upgrades and will encourage timing any significant ruby layer changes with upstream major releases.
Along similar lines, each of these gems is released under identical license terms to the upstream project, thereby simplifying license considerations for consuming applications.