Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Provide instance on condition no other module provides that instance yet

Tags:

java

guice

My application uses Guice for Dependency Injection and consists of several modules, some depend on an instance of class X and some need to be able to run independently of the main application. So in the MainModule I have to provide an instance of class X, while some sub-modules also need to provide that instance, since their respective applications need to be able to run without the MainModule providing said instance of class X. Which leads to errors because "an instance of class X was already bound".

I have been looking around for a while now, but mostly I find references to PrivateModules which don't really do what I need, also I found a lot on OptionalBindings which, as far as I understand, mainly provide default values.

What I need is some sort of conditional binding as in "If another module provides an instance of class X do nothing, if no other module provides an instance of class X provide this one."

like image 896
John Du Avatar asked Jul 11 '19 07:07

John Du


People also ask

Should I use on-demand instances or Reserved instances?

We recommend that you use On-Demand Instances for applications with short-term, irregular workloads that cannot be interrupted. For significant savings over On-Demand Instances, use AWS Savings Plans, Spot Instances, or Reserved Instances .

Where are the services provided in the @ngmodule of the module?

When we provide the service in the @ngModule of the root module or any eagerly loaded module, the will be available everywhere in the application. The Services provided in the @ngModule of the lazy loaded module are available in that module only. Each Injector creates a singleton object of the dependency registered by the provider.

Why does @angular create new instances for each injectiontoken or injectable?

Angular will create new instances for any of InjectionToken or Injectable in cases of using: This is happening because Angular creates a new module Injector for any lazy loaded module, this behavior is perfectly described in docs and this article.

How to return an instance of a service from an existing token?

It can use a factory function, which returns the instance of service class or value ( useFactory ). It can return the instance from an already existing token ( useExisting ).


2 Answers

https://google.github.io/guice/api-docs/latest/javadoc/com/google/inject/util/Modules.html

The override methods are probably what you want

like image 192
Duane Avatar answered Oct 23 '22 09:10

Duane


Isolation

The problem is caused by insufficient isolation of various sub-systems. From a sub-system S point of view, type X has changed its contract over time. In the past, the type gave S ability to control all of its instances. But with integrations, control of instances was lost. Type X changed its behavior in an incompatible way.

Sub-system S should have been more isolated. It should not have used types that are potentially in conflict with other systems. It should have used its own private types instead, there'd be no issue then.

Now let's say in our hypothetical problem, there is a Database sub-system. It uses timeout when issuing network calls. It wants an instance of Timeout that has the value of 100 millis. EmailSender sub-system expects to be slower. Its Timeout has to equal 5 seconds. The systems conflict when integrated.

One approach: private wrapper types

    // EmailSender-private wrapper. Class is not public on purpose.
    class EmailSenderTimeout {
      final Timeout timeout;
      EmailSenderTimeout(Timeout t) { this.timeout = t; }
    }

    // In the module
    bind(EmailSenderTimeout.class)
        .toInstance(new EmailSenderTimeout(Timeout.seconds(5));

    // In the service
    @Inject
    EmailSendingService(EmailSenderTimeout timeout) {
      long millis = timeout.timeout.millis();
    }

Walla! Should anyone ever go and bind Timeout to whatever their heart desires, we the EmailSender still have our 5 seconds!

We achieved through isolation. We are still sharing the Timeout type, but we are no longer sharing instances.

Guicier: binding annotations

This mechanism is Guice's answer to our exact problem.

    // Define this annotation once in the sub-system somewhere.
    // Perhaps even directly in the Module class.
    @Retention(RetentionPolicy.RUNTIME)
    @BindingAnnotation
    @interface ForEmail { }

    // EmailModule
    protected void configure() {
      bind(Timeout.class).annotatedWith(ForEmail.class)
          .toInstance(Timeout.seconds(5);
    }

    // Service
    class EmailSendingService {
      @Inject
      EmailServiceImpl(@ForEmail Timeout timeout) {
        long millis = timeout.millis();
      }
    }

You can reuse the annotation for other shared types, too:

    class EmailServiceImpl {
      @Inject
      EmailServiceImpl(@ForEmail Timeout timeout, 
          @ForEmail RemoteAddress remoteAddress, 
          @ForEmail Protocol protocol) {
      }
    }

Each sub-system would declare its own private binding annotation and use it throughout.

In absolute, no two sub-systems should bind the same types, whether or not they are integrated today.

Simplistic mental model of Guice

There must never be duplicates in bindings:

    class Guice {
      HashMap<Key, Provider> bindings;
    }

    // Combines 3 things: Class, Generic Types, and Annotation
    class Key {
      Class<?> actualClass;
      @Nullable Class<?> annotationClass;
      @Nullable Type genericTypes;
    }

More details: Key.java, TypeLiteral.java

like image 34
iluxa Avatar answered Oct 23 '22 09:10

iluxa