guiceguice-3

How can I wire up N chains of nodes in guice like filters


I want to create node A(multiplexor) which has N node B's. Each node B has it's own node C and each node C has it's own node D and each node D has it's own node E.

Let's say N=4 on number of B,C,D,E chains that A has. Ideally, each node E ends up with information like i=0, 1, 2, 3.

On top of this, I may want to re-order B, C, D as they are pretty much like filters so I have them all implementing an interface with

Response service(Request r);

I would like desperately to stay away from assisted inject as incoming developers always get confused by that (at least I have noticed this time and time again and am tired of teaching that and I too think it's a bit ugly and confusing). They seem to need no training on all the other stuff making it easy.

I am thinking maybe I just inject a Provider and B has an C, C has a D and then they all have start methods but that sort of didn't pan out as I had hoped since the start method has to change on every service and all their start methods have to match. See, the issue is Node A has information on what node number E is and needs to get that information to E, but B, C, and D don't need that information.

I could maybe do some wiring in the A constructor and have

Provider<B> bProvider
Provider<E> eProvider

but then, how do I get E all the way down the chain. I am not quite sure of a clean way of doing this all.

thanks, Dean


Solution

  • I can think of three ways to do this: Child injectors, manual DI, and assisted injection.

    Child injectors

    This is probably the best option, also listed in my answer two years ago.

    Taking a cue from the Guice documentation on the "robot legs problem", you could create a child injector that allows your @NodeNumber int binding anywhere in your tree that you'd like. This means your bindings and structure don't have to change much, even if the filter order changes, or if your node number is later needed in C, D, F, or any dependency thereof. B, C, D, an E won't be injectable until you provide a NodeNumber binding, but that's probably correct for what you're describing.

    Naturally, you could use @Named("nodeNumber") instead of defining a @NodeNumber binding annotation, or you could refactor into a BFactory you save elsewhere.

    class E {
      @Inject @NodeNumber int nodeNumber;  // Available anywhere in your graph!
    }
    class D { @Inject E e; }
    class C { @Inject D d; }
    class B { @Inject C c; }
    class A {
      @Inject Injector injector;  // You can always inject the Injector!
      B addNode(final int nodeNumber) {
        // Create a new injector
        return injector.createChildInjector(new AbstractModule() {
          @Override public void configure() {
            bindConstant().annotatedWith(NodeNumber.class).to(nodeNumber);
          }
        }).getInstance(B.class);
      }
    }
    

    Manual DI

    If you only have a few dependencies that change infrequently, you can create your own stack manually. This may not use Guice to its full potential, but it's perfectly clear what's going on here, and what needs to change if B/C/D/E gain or lose any deps.

    class A {
      @Inject Provider<SomeDepOfB> bDep;  // If anything in the tree has other deps,
      @Inject Provider<SomeDepOfC> cDep;  // you'll need to provide them yourself.
      @Inject Provider<SomeDepOfD> dDep;
    
      B addNode(int nextNodeNumber) {
        return new B(
            bDep.get(),
            new C(
                cDep.get(),
                new D(
                    dDep.get(),
                    new E(nextNodeNumber))));
      }
    }
    

    Assisted injection

    You said you were trying to avoid this, but I'm listing it here for completeness.

    class E {
      interface Factory { E create(int nodeNumber); }
      E(@Assisted int nodeNumber, SomeDep1OfE dep1, SomeDep2OfE dep2) { /* ... */ }
    }
    
    class D {
      interface Factory { D create(int nodeNumber); }
      D(@Assisted int nodeNumber, E.Factory eFactory) { /* ... */ }
    }
    
    // ...
    
    class YourModule extends AbstractModule {
      @Override public void configure() {
        install(new FactoryModuleBuilder().build(E.Factory.class));  // Binds E.Factory
      }
    }
    

    Though assisted injection may not be the right choice here, its use is pretty straightforward and it can take away a lot of boilerplate (particularly if E has a lot of other deps, etc).