a_guest a_guest - 19 days ago 4
Python Question

How to inject a variable number of similar components that have dependencies themselves?

Background



I want to realize dependency injection in Python using injector (or pinject) which itself heavily borrows from guice. While an answer using
Python/injector
would be ideal, I'm also very happy about solutions/approaches that feature
Java/guice
.

Intention



I'll give you a quick summary of what I want to achieve: I have a component that depends on a list/sequence of other components that all implement the same interface. Those components have dependencies themselves which may vary amongst the different implementations. The concrete types (implementations) shall be configurable by the user (or using any mechanism of the DI framework).

Example



Yes, I've read Modules should be fast and side-effect free which suggests not to use an XML file for configuration, however as I don't know how to realize this within the framework I'll use one to demonstrate the dependency structure:

<RentingAgency>
<Vehicles>
<Car>
<DieselEngine></DieselEngine>
</Car>
<Car>
<PetrolEngine></PetrolEngine>
</Car>
<Bike></Bike>
</Vehicles>
</RentingAgency>


In this example there is a renting agency (the component that depends on a list of others) that rents out all kinds of vehicles (the interface). The specific vehicles in their fleet (in this case two cars and one bike) should be configurable but fixed during runtime. The vehicles themselves can have dependencies and they can be different depending on the type of vehicle (a car depends on a motor, a bike has no dependencies in this case).

Question



How can I construct the renting agency within the DI framework so that all required vehicles are injected and their dependencies resolved properly?




Maybe helpful



Multibinder



I've read about Multibinder (
injector
seems to have something similar with
Binder.multibind
) which allows for injecting a collection of objects that implement the same interface. However:


  • Can it be used to create multiple instances of the same class that need to receive different dependencies (the two cars (
    Class Car
    ) in the example have different motors:
    Interface Motor
    ,
    Class DieselEngine
    ,
    class PetrolEngine
    )?

  • Using providers to accomplish that task seems to me like giving up the benefits of dependency injection: I could manually create the
    Car
    instances in the provider, passing the required
    Motor
    as argument, however because this pattern repeats further down the chain (i.e. multiple
    Motor
    s of the same type are used and they also have dependencies) I want to use dependency injection for generating those objects too. But to manually use them in the provider it seems to me like I have to obtain the instances directly from the injector. The docs mention that injecting the injector is a rare case and from my understanding of dependency injection, the great benefit is that one can request a component and all dependencies are resolved by the framework automatically.

    Also because I actually use Python I'm not sure if this approach is appropriate (as Python is quite flexible when it comes to dynamic code generation). Also
    injector.Injector.get.__doc__
    mentions




Although this method is part of :class:
Injector
's public interface
it's meant to be used in limited set of circumstances.
For example, to create some kind of root object (application object)
of your application (note that only one
get
call is needed,
inside the
Application
class and any of its dependencies
:func:
inject
can and should be used):

Answer

Dependency injection frameworks are primarily for dependencies and because your Vehicles object is configured by the user at runtime it is more like application data than a dependency. It probably can't just be injected in one shot using MultiBinding unless you know it at compile time.

Likewise, you are right in saying that it would not be a good approach to construct your set of components by iterating and calling injector.getInstance(Bike.class) etc. For one, this is not good for testing.

However, because the objects contained in Vehicles have their own dependencies you can leverage the DI framework in the creation of your Vehicles object. Remember, also, that although you cannot bind a Provider to an implementation, when you bind a key Guice will inject that provider for you.

For the simple example in the post, consider creating a VehicleFactory. Inside, you could have something like the following:

public class VehicleModule implements Module {
    @Override
    public void configure(Binder binder) {
        binder.bind(DieselEngine.class).toProvider(DieselEngineProvider.class);
        binder.bind(PetrolEngine.class).toProvider(PetrolEngineProvider.class);
        binder.bind(Bike.class).toProvider(BikeProvider.class);
    }
}

public class DieselEngineProvider implements Provider<DieselEngine> {

    @Inject
    public DieselEngineProvider() {
        //if DieselEngine has any dependencies, they can be injected in the constructor
        //stored in a field in the class and used in the below get() method
    }

    @Override
    public DieselEngine get() {
        return new DieselEngine();
    }
}

public class VehicleFactory {

    private final CarFactory carFactory;
    private final Provider<Bike> bikeProvider;

    @Inject
    public VehicleFactory(CarFactory carFactory, Provider<Bike> bikeProvider) {
         this.carFactory = carFactory;
         this.bikeProvider = bikeProvider;
    }

    public Bike createBike() {
        return bikeProvider.get();
    }

    public Car createDieselCar() {
         return carFactory.createDieselCar();
    }

    public Car createPetrolCar() {
         return carFactory.createPetrolCar();
    }
}

public class CarFactory {
    private final Provider<DieselEngine> dieselEngineProvider;
    private final Provider<PetrolEngine> petrolEngineProvider;

    @Inject
    public CarFactory(Provider<DieselEngine> dieselEngineProvider, Provider<PetrolEngine> petrolEngineProvider) {
        this.dieselEngineProvider = dieselEngineProvider;
        this.petrolEngineProvider = petrolEngineProvider;
    }

    public Car createDieselCar() {
        return new Car(dieselEngineProvider.get());
    }

    public Car createPetrolCar() {
        return new Car(petrolEngineProvider.get());
    }
}

As you mention, there is the danger of this becoming 'factories all the way down', but Guice can help you here.

If the production of Engine becomes more complicated and involves a combination of different parameters, you can use tools like AssistedInject to auto-create the factories for you.

If you end up with a set of common dependencies and uncommon dependencies that you want to use to create different 'flavours' of an object then you have what is known as the robot legs problem then Guice can solve it using private modules.

Do note the following caveat from the Dagger 2 user guide:

Note: Injecting Provider has the possibility of creating confusing code, and may be a design smell of mis-scoped or mis-structured objects in your graph. Often you will want to use a factory or a Lazy or re-organize the lifetimes and structure of your code to be able to just inject a T.

If you follow this advice, it would seem that you would have to carefully balance using providers and using factories to create your Vehicle.

Comments