HubSpot's Product team writes much of our backend code in Java, with Google Guice providing depedency injection. We've created modules to handle most common tasks, modules which we can access with no more than an @Inject annotation.  

Recently, though, we've been working with Elasticsearch, and have run into an issue reusing our existing modules.  Elasticsearch also uses Guice, but it uses a shaded version; instead of importing com.google.inject.*, it imports org.elasticsearch.common.inject.*.  When evaluating dependencies for injection, Elasticsearch requires modules that extend org.elasticsearch.common.inject.AbstractModule, instead of com.google.inject.AbstractModule.  As our existing modules extend com.google.inject.AbstractModule, Elasticsearch isn't able to use them directly.

We ended up working around this by creating a new module, extending the org.elasticsearch version of the Guice AbstractModule, which instantiates a com.google.inject.Injector instance and provides the shared modules that we need.  This ends up looking like this:

package com.hubspot.elasticsearch.plugin.lib;

import org.elasticsearch.common.inject.AbstractModule;
import org.elasticsearch.common.inject.Provides;
import com.hubspot.config.HubSpotConfigModule;
import com.hubspot.other.OtherModule;

public class BaseInjectorModule extends AbstractModule {

  private com.google.inject.Injector baseInjector;

  @Override
  protected void configure() {
    this.baseInjector = com.google.inject.Guice.createInjector(
            new HubSpotConfigModule(),
            new OtherModule());
  }

  @Provides
  public HubSpotConfig HubSpotConfigProvider() {
    HubSpotConfig hsConfig = baseInjector.getInstance(HubSpotConfig.class);
    return hsConfig;
  }

  @Provides
  public Other OtherProvider() {
    Other otherThing = baseInjector.getInstance(Other.class);
    return otherThing;
  }
}

Having done this, we can install the module in the context of our Elasticsearch plugin, by overriding the modules() method of org.elasticsearch.plugins.AbstractPlugin:

package com.hubspot.elasticsearch.plugin.MyPlugin;

import org.elasticsearch.plugins.AbstractPlugin;
import org.elasticsearch.common.inject.Module;
import org.elasticsearch.common.collect.Lists;
import java.util.Collection;
import com.hubspot.elasticsearch.plugin.lib.BaseInjectorModule;

public class MyPlugin extends AbstractPlugin {

  @Override
  public Collection<class<? extends Module>> modules() {
    Collection<class<? extends Module>> modules = Lists.newArrayList();
    modules.add(BaseInjectorModule.class);
    return modules;
  }
}

The end result of this work: we're able to reuse our existing code, and maintain consistency in how we resolve dependencies, despite the shaded version of Guice being used in Elasticsearch.

We took this approach because there wasn't an obvious canonical solution.  We're curious, though: how have other people solved this problem?  We can't imagine that we're alone in having run into it.  

Recommended Articles

Join our subscribers

Sign up here and we'll keep you updated on the latest in product, UX, and engineering from HubSpot.

Subscribe to the newsletter