alwaysAStudent
alwaysAStudent

Reputation: 2274

Guice configure scope

I am new to Guice and trying to understand the scope of configure() method in your Module class. Currently below is my application structure. It works.

class MainClass {
     public static void main(String[] args) {
         Injector injector = createInjector(new MainModule(param1, param2, param3));
         injector = injector.createChildInjector(injector.getInstance(FuncModule.class));

     }
}

FuncModule.java

 class FuncModule extends AbstractModule {

   @Override
   public void configure() {

         // Register a AWS SWF Workflow Worker
         // Register a AWS SWF Activity Worker

         // Instantiate WorkflowFactory class
         TempWorkflowClientExternalFactory obj = new TempWorkflowClientExternalFactoryImpl(<param1>, <param2>);
         bind(TempWorkflowClientExternalFactory.class).annotatedWith(Names.named("temp1")).toInstance(obj); 
   }
 }

I am trying to understand if my configure method is doing "too much". Is the intention/scope of configure method only limited to binding? If so, where would be best place to register the workers and instantiate the factory object?

Upvotes: 1

Views: 2808

Answers (1)

Jeff Bowman
Jeff Bowman

Reputation: 95634

You're right to wonder whether your configure method is the right place for this type of initialization; it's a matter of judgment. Guice does have this to say: Modules should be fast and side-effect free.

But the full power of the Java language comes at a cost: it's easy to do too much in a module. It's tempting to connect to a database connection or to start an HTTP server in your Guice module. Don't do this! Doing heavy-lifting in a module poses problems:

  • Modules start up, but they don't shut down. Should you open a database connection in your module, you won't have any hook to close it.
  • Modules should be tested. If a module opens a database as a course of execution, it becomes difficult to write unit tests for it.
  • Modules can be overridden. Guice modules support overrides, allowing a production service to be substituted with a lightweight or test one. When the production service is created as a part of module execution, such overrides are ineffective.

A few other factors to consider:

  1. Your Guice module is still a Java class, and can and should adhere to the Single Responsibility Principle as much as with any other class. If the construction or configuration of your dependencies is more than a screenful or two, or is hard to describe in a sentence or two, it might be time to break up the module (see install) or extract the construction/configuration to its own method or class. You've given us a hint with your commented configure sections: Maybe it's time to extract methods or classes so the code is more self-describing.

  2. Guice is just one dependency injection framework, and is designed to require very little Guice-specific code or patterns. With JSR330-compatible annotations (@Inject) and interfaces (Provider), you should be able to replicate or replace Guice's functionality manually—or with a different framework like Spring or Dagger (2)—without much trouble. Java Module instances, however, are unique to Guice; custom initialization code that lives in a Module will need to be refactored or rewritten if you ever want to use it in a non-Guice context. This may be a good reason to separate reusable initialization code from Guice modules in the first place.

  3. Along similar lines, as the Guice wiki mentions, you should test any nontrivial logic in your classes. Guice Modules and their configure methods are hard to test without Guice; you may find it easier to test your external construction/configuration if it's in a separate class or method.

  4. Any initialization you do in a Module's configure method happens when you call createInjector or createChildInjector, along with all other Modules in an unspecified order. This gives you very little granularity to set up logging, defer to a background thread, catch exceptions gracefully, or otherwise control when and how the initialization happens. By extracting third-party initializer code to a separate class or method, or a Provider or @Provides method, you give yourself more flexibility about when and how it is run.

  5. All that said, Guice definitely allows for instance bindings, and it's very common to see simple and lightweight constructor calls, initializer calls, and other instance-preparing code in Modules. Creating a whole new class for a couple of lines of trivial initialization is probably overkill.

In short, leave in simple/short/safe initialization calls, but as soon as things get complicated/long/dangerous be ready to extract the creation or initialization to give yourself more control.


P.S. Separately, though there's nothing technically wrong with obtaining a Module instance from an Injector, be aware that it is not a common pattern. At best, this violates the rule-of-thumb that you don't have access to a functioning Injector at Module configure time; at worst, you may find it hard to reason about which Injector you're actually calling, and thus which bindings you have available to you. You can keep it, but use with caution, and consider keeping things explicit by passing parent-injector-sourced dependencies as Module constructor parameters.

/** Injected child module. Get this from a parent injector. */
public class BModule extends AbstractModule {
  @Inject Injector injector;  // You can always inject the Injector...

  @Override public void configure() {
    bind(B.class).to(BImpl.class);
  }

  @Provides C provideC(B b) {
    return new C(b);
  }

  @Provides D provideD() {
    return new D(injector.getInstance(B.class));  // but this won't work;
                                                  // injector is the parent,
                                                  // not the one with this module.
  }
}

Upvotes: 1

Related Questions