Guice injection with Builder pattern for client lib

4.5k views Asked by At

I am new to Google Guice and am trying to wrap my head around how to use it for my particular scenario. I am building a service client that is pretty complex and that (I believe) truly requires a Builder Pattern to be instantiated correctly. Because this client will ultimately get packaged up into its own JAR lib, I would like Guice to handle the DI under the hood. Below is a greatly simplified version of my code:

public interface MyClient {
    public FizzResource getFizzResource();
    public BuzzResource getBuzzResource();
    public FooResource getFooResource();
}

public class MyClientImpl implements MyClient {
    // See below
}

public class GetFizzCommand {
    // omitted for brevity
}

public class GetBuzzCommand {
    // omitted for brevity
}

public class GetFooCommand {
    // omitted for brevity
}

public interface FizzResource {
    Fizz getFizz(Long fizzId);
}

public class FizzResourceImpl implements FizzResource {
    private GetFizzCommand getFizzCommand;

    @Override
    Fizz getFizz(Long fizzId) {
        return getFizzCommand.doGetFizz(fizzId);
    }
}

public interface BuzzResource {
    Buzz getBuzz(Long buzzId);
}

public class BuzzResourceImpl implements BuzzResource {
    private GetBuzzCommand getBuzzCommand;

    @Override
    Buzz getBuzz(Long buzzId) {
        return getBuzzCommand.doGetBuzz(buzzId);
    }
}

public interface FooResource {
    Foo getFoo(Long fooId);
}

public class FooResourceImpl implements FooResource {
    private GetFooCommand getFooCommand;

    @Override
    Foo getFoo(Long fooId) {
        return getFooCommand.doGetFoo(fooId);
    }
}

So as you can see the hierarchy/dep graph is as follows:

  • MyClient should be injected with *ResourceImpls
  • Each *ResourceImpl should be injected with a *Command instance

The intended use case is to make building a MyClient impl as easy as:

MyClient myClient = MyClientImpl.Builder("myservice.example.org", 8080L, getWidget())
    .withAuth("user", "password")
    .withHttps()
    .withFailureStrategy(someFailureStrategy)
    // ...etc.
    .build();

So here's my best attempt at the MyClientImpl, its internal builder and my Guice module:

public class BaseClient {
    private String uri;
    private long port;
    private Widget widget;

    // ctor, getters and setters
}

public class MyClientImpl extends BaseClient implements MyClient {
    @Inject
    private FizzResource fizzResource;

    @Inject
    private BuzzResource buzzResource;

    @Inject
    private FooResource fooResource

    public MyClientImpl(String uri, long port, Widget widget) {
        super(uri, port, widget);
    }

    public static class Builder {
        private String uri;
        private long port;
        private Widget widget;

        Builder(String uri, long port, Widget widget) {
            super();

            setUri(uri);
            setPort(port);
            setWidget(widget);
        }

        // Lots of methods on the builder setting lots of MyClient-specific properties
        // that I have omitted here for brevity.

        MyClient build() {
            Injector injector = Guice.createInjector(new MyClientModule(this));
            return injector.getInstance(MyClient.class);
        }
    }
}

public class MyClientModule extends AbstractModule {
    private MyClientImpl.Builder builder;

    public MyClientModule(MyClientImpl.Builder builder) {
        super();

        setBuilder(builder);
    }

    @Override
    protected void configure() {
        MyClientImpl myClientImpl = new MyClientImpl(builder.getUri(), builder.getPort(), builder.getWidget());

        bind(MyClient.class).toInstance(myClientImpl);
    }
}

But for the life of me, I can't see how/where to:

  • Bind the *Commands to the *ResourceImpls; and
  • Bind the *ResourceImpls to the MyClientImpl instance

Any ideas?

1

There are 1 answers

0
The111 On BEST ANSWER

It's not clear entirely what you're trying to accomplish with your injections. You may be confusing the matter further by making it about builders and other aspects of your design. I suggest you strip your code down to a SSCCE that functions but does not include any injection, and then increment your design by injecting, for example, a single field. Right now all your code shows is the trimmed down skeleton of a complex design with injections that don't make sense, and there is no way to run the code and see what happens. Guice operates at runtime, so it's essential that we see your main method to see what your application code actually does.

Below follows a very simple example of what I describe above.

An interface with two different impls:

public interface Service {
  void useService();
}

public class FooService implements Service {
  public void useService() {
    System.out.println("foo service used");
  }
}

public class BarService implements Service {
  public void useService() {
    System.out.println("bar service used");
  }
}

A vanilla (non-Guice) user of this interface:

public class ServiceUser {
  Service service = new FooService(); // change impl to be used here

  public static void main(String[] args) {
    new ServiceUser().service.useService();
  }
}

A Guicified user of this interface:

public class ServiceUserGuicified {
  @Inject
  Service service;

  public static void main(String[] args) {
    Injector injector = Guice.createInjector(new FooServiceModule());
    ServiceUserGuicified user = injector.getInstance(ServiceUserGuicified.class);
    user.service.useService();
  }
}

public class FooServiceModule extends AbstractModule {
  @Override
  protected void configure() {
    bind(Service.class).to(FooService.class);
  }
}

In the Guicified version, the impl definition is coded in a Guice module rather than in the runtime code. The only thing the runtime code includes is the usage of the module when creating the injector. You could make a new module for bar service and use that to create an injector instead, or you could change the binding in your primary service module.

Once you understand what is happening above, you should be able to solve any issues in your own code by repeating the concepts on every instance that you want to be injected by Guice.