Guice for the Rubyist

June 26, 2013

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 Guice-based framework.

Guice

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 ActiveRecord::Base.connection, Capybara.app, and 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.)

Injectable

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"

You can run this in a JRuby irb console on your machine if you download injectable.rb, javax.inject-1.jar, and guice-3.0-no_aop.jar.

As you peruse the code, you may notice subtle calls to java_class and 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.

I write code, eat plants, and run. Sometimes.
Co-Inventor of Square Wallet, Engineer

Comments

Get support help at squareup.com/support. We'll delete off-topic comments.