Guice for the Rubyist
Square heavily utilizes Guice in its Java codebase. Rather than fully wire
Ruby into the dependency graph, we've built a small API to simulate field
injection onto our Ruby objects. This allows our developers to write
native-feeling Ruby code while still providing access to the underlying
Let's take a moment to talk about Guice from the Rubyist's point of view. In
Ruby applications, the common idiom is to provide global access to the objects
your code depends on: consider
RSpec.configure. Java applications tend to use dependency injection
instead, passing collaborators into constructors as needed. We can do that in
Ruby (often to great effect), but it works so much more pleasantly in Java
because the type system allows dependency injection libraries, like Guice, to
automatically determine your classes' dependencies, and their dependencies, all
the way down. Guice can then instantiate your entire object graph for you.
If we were to write something like that in Ruby, it might look like this:
class Injector def initialize(registrations) @registrations = registrations end def (key) klass = @registrations[key] constructor = klass.method(:initialize) # Let's pretend Ruby let us do this: arguments = constructor.each_argument_type.map do |type| self[type] # recurse! end klass.new(*arguments) end end
(Of course, it gets much more complicated than that.)
In our integration framework (Minecart), we've brought Guice-backed dependency
injection to our Ruby code. In general, we aim to insulate our Ruby
applications from needing to speak (or even know about) Java methods and types
by providing small, native-feeling APIs. But to support developers who need
access to an underlying Guice-bound Java object that we've not yet exposed, we
provide something like field injection.
Here's what it looks like:
# JRuby's So Awesome! require 'javax.inject-1' require 'guice-3.0-no_aop' require 'injectable' class DemoApp include Minecart::Injectable inject :hello_world, :with => java.lang.String def demo puts hello_world end end class DemoModule < com.google.inject.AbstractModule def configure bind(java.lang.String.java_class).toInstance("Hello World") end end injector = com.google.inject.Guice.createInjector(DemoModule.new) Minecart::Injectable.set_injector(injector) DemoApp.new.demo # => "Hello World"
As you peruse the code, you may notice subtle calls to
to_java both above and in
Injectable. Suffice it to say those were some
hard-won keystrokes. We'll share those war stories another day.
This post is part of a series, which highlights discoveries and insights
found while integrating Ruby with our robust Java stack.