Dependency Injection

Content

Forewords:

One goal of foundationj is to facilitate the development of a modular application which supports collaborative development. We expect such an application to be composed of multiple modules contributed by developers who know little about each other.

This implies that the following requirements need to be incorporated into the architecture of the application:

In order to help meeting the above requirements, foundationj provides a component-based architectural design paradigm as well as runtime support to help articulate individual components together.

The paragraphs below describe important design structures that foundationj provides support for.

Constructor injection

Constructor injection (a form of dependency injection) is a design pattern which is used to remove dependencies from a class to specific implementations. As you will see, this has a lot of advantages.

To illustrate this we will use a simple example:

BikeShop Example part 1

In this example we are asked to provide a Shop implementation using the Shop Api in blue. The shop interface looks like this:

public interface Shop{
	// Returns the types of goods sold by this Shop
    public String goods();
    // Sell something to the customer
    public void sell(Customer customer);
}

Our shop is selling bikes so we also depend on the BikeFactory API. Our sell method can be implemented like so:

    // class fields
    private BikeFactory bikeFactory; // Builds the bikes
    private ShopManager manager;  // Takes care of the customers, handles treasury, etc...    
    // Type of goods
    public String goods(){
		return "BIKES";    
    };
    // sell method
    public void sell(Customer customer){
        List<BikeModel> availableModels = bikeFactory.getAvailableModels();
        BikeModel chosenModel = customer.chooseModel(availableModels);
        if(chosenModel!=null){ // Customer might not like our models...
            Bike bike = bikeFactory.build(chosenModel);
            manager.makeTransaction(customer, bike);
        }
    }

Now the important question is how do we obtain a reference to our BikeFactory and ShopManager? Without dependency injection, we need to create these instances ourselves. In this example we decided to implement the ShopManager interface ourselves (BikeShopManager) so we can create a new BikeShopManager in our BikeShop constructor. After digging into the distinct components of the application, we found a Bike library which provides an implemention for the BikeFactory. The Bike Factory of this library does not produce all bike components itself and instead use subcontractors to obtain these components. The Bike Library thus depends on another library which supplies the bike components.

Given these details, the constructor of our BikeShop looks like this:

public class BikeShop implements Shop{
    
    private final BikeFactory bikeFactory; 
    private final ShopManager manager; 
    
    public BikeShop(){
        bikeFactory = new BikeFactoryImpl(new BikeComponentSupplierImpl()); // From libraries
        manager = new BikeShopManager(); // From our own implementation
    }
}

And our dependency graph now looks like this:

BikeShop Example part 2

The necessity for us to create an instance of the BikeFactory has unfortunately several disadvantages:

  1. This ties our BikeShop to a specific library: In order to change the BikeFactory used by our BikeShop, we need to modify the code, there is no way of configuring our class externally.
  2. It also increases the risk of having a class which is unstable. In this particular case we created 2 dependencies: to the bike library and to the BikeComponent library. Changes in any of these libraries may require to update the code of our BikeShop class. Having more dependencies means higher code maintenance requirements.Increasing dependencies also increase complexity and obscurs the comprehension of how changes in the code propagate to other parts of the application.
  3. Finally, when we designed our BikeShop class, we needed to be aware of the libraries that were available in the full application. In a complex application where several teams develop their own plugins, this becomes difficult and time consuming to keep track of all possibilities.

Constructor injection can solve these issues altogether: Instead of creating the BikeFactory instance ourselves, we ‘declare’ the BikeFactory as an argument to our constructor. The task of finding a proper BikeFactory instance becomes the responsibility of the framework:

public class BikeShop{
    
    private final BikeFactory bikeFactory; 
    private final ShopManager manager; 
    
    // Constructor 1
    public BikeShop(BikeFactory injectedBikeFactory){
        manager = new BikeShopManager(); // our own implementation, although CI could be used for this as well
        this.bikeFactory = injectedBikeFactory; // just reference the provided factory
    }
    
    // Constructor 2
    public BikeShop(List<BikeFactory> injectedbikeFactories){
        manager = new BikeShopManager(); // our own implementation
        this.bikeFactory = manager.chooseFactory(injectedBikeFactories); // let our manager choose among available factories
    }
    
}

Notice that with this example we also illustrate that it is possible to define several constructors. Here we added a constructor accepting a List of BikeFactories (constructor 2). We may anticipate that several implementations will be available in the application assembly. Since we decided that our shop should only rely on one unique factory for bike supplies, we made our ShopManager choose which factory is the most appropriate to rely upon.

This simple change has several consequences:

  1. We removed the dependencies to the specific implementation of BikeFactory. Changes in these libraries will not affect our class anymore. Our class is more stable.
  2. When designing the class we no longer need to worry about finding libraries providing implementations for the BikeFactory we need. Class design is facilitated.
  3. Finally, the BikeFactory used by our class at runtime can easily be changed. Control of which BikeFactory will be used is moved outside of the class. Note that in this particular example, we made our ShopManager choose one of the factories so there is still a level of control in our implementation but the point is that the provided BikeFactories can be changed entirely without affecting our code. Our implementation becomes configurable and more reusable.

BikeShop Example part 3

Since the definition of the BikeFactory implementations to be provided to the BikeShop instance is made outside the class, the next chapter explains where and how this configuration can be controlled.

Containers and context

When a foundationj application is launched, the boot process creates a container. Given a collection of classes, called components, a container is capable of resolving dependencies in order to instantiate the components when they are needed (As we saw above, dependencies are defined as arguments in constructors). For example, if we create a container and register the following classes: BikeShop, BikeFactoryImpl, BikeComponentSupplierImpl, the container will resolve dependencies and create the BikeShop instance using a BikeFactoryImpl instance, itself provided with an instance of BikeComponentSupplierImpl. Other examples can be found here.

NB : Complex dependency graphs can be resolved by a container, however, all dependencies must be satisfied and there should be no cyclic dependency.

Although the flexibility of this solution is obvious, one might think that since components have to be registered individually in code, this solution leads to a master class hard-coding the registration (and which ends up depending on everything..).

Of course, this does not have to be the case. In foundationj, we use annotations to perform the configuration:

In fact, annotations fulfill three main purposes in foundationj:

  1. Discovery: They define which classes are component ‘entry points’ and they enable the discovery of these classes during the assembly scanning process at boot time.
  2. Scope: They specify a hierarchy of containers in order to facilitate the ‘life cycle’ management of components at runtime (see the next chapter).
  3. Verifications: Finally, they can be used by the application designer to enforce a number of conventions to be respected by plugin contributors. Notably, annotations activate code checks at compile time in order to catch configuration issues early in the development process.

In our BikeShop example, the API designer has annotated the Shop interface with @Modular (TODO link to javadoc entry). This annotation specifies that the annotated interface should be implemented by a component ‘entry class’ and that implementing classes should be themselves annotated with @Module:

//------------------------//
//         API            //
//------------------------//
@Modular
public class Shop{
	[...]
}
//------------------------//
//         Impl           //
//------------------------//
@Module
public class BikeShop implements Shop{    
   [...]    
}

With these annotations in place, compilation checks are performed by an annotation processor (TODO link to Module processor) which also generates entries in the META-INF directory of the compiled jar files. These entries are then used at boot time when scanning for the Modular interfaces and all their implementations.

For example when compiling our code, the generated ShopImpl.jar will contain a folder ‘foundationj’ with 1 text file named ‘Shop’ (the name of the modular interface that we implement) and with one line: ‘our.package.name.BikeShop.class’. At boot time, if the ShopImpl.jar is located on the classpath, the boot process will ‘know’ that the BikeShop.class is an available implementation for the Shop interface should any other module depend on Shop.

With this system, The assembly dictates runtime composition:

Given the following structure where all components are compiled within their own jar:

BikeShop Example part 4

The boot process will create a container configured like so:

BikeShop Example part 5

and BikeShop constructor 1 will be used at runtime:

    // Constructor 1 used at runtime
    public BikeShop(BikeFactory injectedBikeFactory){ // <= BikeFactoryImpl instance provided at runtime
        manager = new BikeShopManager();
        this.bikeFactory = injectedBikeFactory;
    }

If we add another jar in the classpath with an alternative implementation:

BikeShop Example part 6

The boot process will create a container configured with both implementations:

BikeShop Example part 7

and BikeShop constructor 2 will be used at runtime:

    // Constructor 2 used at runtime
    public BikeShop(List<BikeFactory> injectedbikeFactories){ // <= List with BikeFactoryImpl and AltBikeFactory provided
        manager = new BikeShopManager(); // our own implementation
        this.bikeFactory = manager.chooseFactory(injectedBikeFactories);
    }

This system has 3 advantages:

  1. The expected components entries are clearly identifiable as they can be found by looking for annotated interfaces in the API, so this should make life easier for developers.
  2. When a class is annotated with @Module, compilation fails if the class implements 0 or more than one @Modular interface. This allows the API designer to provide guidance on the use of the API.
  3. The configuration is self-descriptive and simply assembly dependent.

Scopes and Application Control

The application assembly and discovery process is not the only mean to control how containers are configured.

As shown in the previous section, we can use foundationj annotations to define the components of an application and to wire them up together. Now, we can also group these components into multiple cohesive units (containers) that we can start (instantiate) and stop (garbage collect) so that the lifetime of components from the same unit are managed altogether. We can also create a hierarchy for these containers with the following relationships rules:

  1. Children containers can obtain instances from the parent but not the opposite.
  2. When a parent is stopped all its children are stopped beforehand.

With these rules in place, the management of the various phases of the application can be greatly simplified while remaining flexible.

To illustrate this, we will describe a fictional application and will move down its container hierarchy to introduce some important properties.

Imagine an application which can simulate a village, with a street map, multiple building types, factories, inhabitants etc… This application would give the possibility to a user to explore this village by navigating a map and to participate in its economy by buying items from shops.

A possible container hierarchy for this application could be the following one:

Containers Hierarchy

In this diagram, each container is represented by a rounded rectangle with a dashed contour. The list of classes shown in the top right corners corresponds to the components entry class that the container manages. The interfaces that these classes implement are denoted with an icon. For example, in the VISITOR container, the shop interface is illustrated with the yellow cart icon showing that there are 2 shop implementations BikeShop and ToolShop. The roles of each container is also shown in the bottom section of each box.

The other elements of this diagram will be discussed in the following dedicated sections:

(I recommend right-clicking on the diagram and selecting ‘Open link in a new tab’, this way you can go back and forth between the diagram and the ext in this tab) .

The @Scope annotation

In code, the hierarchy is specified using the @Scope annotation. @Scope possesses 3 elements:

The target of the @Scope annotation are interfaces annotated with @Modular (or @Core, see the javadoc).

	@Modular
	@Scope(name="VILLAGE", parent="APPLICATION", policy=ScopePolicy.SIBLING)
	public interface Shop{ ... }

and the implementing class(es) inherit the scope properties of the interface

	@Module // Inherits the Scope!
	public class BikeShop{ ... }

It is the API designer’s responsibility to define the container hierarchy of the application. It is possible that future versions of foundationj will allow implementations to override the Scope of the implemented interface, but this still needs to be given some thoughts.

A Hierarchy for components life expectancy

As mentioned earlier, the advantage of creating a hierarchy is to simplify the management of the distinct phases of the application (and its intelligibility). The container tree is organised in such a way that long-lived containers are placed at the top of the hierarchy and shorter lived containers further down the hierarchy.

In our example, the root is APPLICATION. This is the root for 2 reasons: 1) because it is the application entry point - it provides a front page to allow users to choose a village simulation to explore - and 2) because it manages the DataStore which will need to be available for the entire life span of the application. The VILLAGE container comes next because the state of a simulated village must be maintained at least for as long as visitors are present in the village. So visitors have a shorter life span than the village, and the views (map or shop views) which are specific to each visitor have an even shorter life span.

By grouping components into these units, the management of the distinct phases of the application remains clear. Only a few nodes are sufficient to control the application. If a VISITOR decides to exit the VILLAGE, all the views it had previously created will be shut down first before the VISITOR container turns off. Similarly, if a VILLAGE simulation stops, all the VISITOR will exit the VILLAGE first. This hierarchy system makes it as easy as turning off a switch in an electrical circuit.

The Hierarchical Singleton pattern

As we said earlier, children containers can obtain instances from parents. The DataStore instance created by the APPLICATION container will therefore be available to its children containers. It is this property which can be exploited to create a singleton pattern:

Lets look at the VILLAGE container to illustrate this. The simulation is managed by the VillageModelImpl instance. Its constructor looks like this:

	// VillageModel Constructor
	public VillageModelImpl(List<Shop> shops, DataStore dataStore){ // injected dependencies
		... // initialise the model
	}

The first argument is a list of Shop instances so the model can delegate internal shop management to injected implementations. In this case the list will contain a new BikeShop instance and a new ToolShop instance.

The second argument is the DataStore so that the model can obtain some persisted data like the location of the shops, number of inhabitants etc… The important thing is that the injected DataStore instance will be obtained from the parent container.
This is important because, with this design, if we decide to start another VILLAGE container, a new VillageModelImpl would be created using new instances of BikeShop and ToolShop. However, the DataStore instance would be the same as the one provided to the first model since it is obtained from the parent container. In other words, the DataStore instance is a Singleton instance at the level of the application. If instead we wanted to run different models on distinct DataStore instances, the DataStore interface should be scoped inside VILLAGE.

The container hierarchy allows for more flexibility than the classic singleton pattern. For example, lets say that we start 2 village simulations, and that in each village several distinct visitors are spending time exploring the villages. A village can accept several visitors but a specific visitor cannot explore 2 villages at the same time, each visitor should be given the possibility to see one unique village only. In other words, we need ‘scoped singleton instances’ of VILLAGE. Each VILLAGE instance should be shared by downstream containers but not by sibling containers. This is exactly what the tree hierarchy enables:

graph BT v1[visitor 1] -- 'Can See' --> sea((Seashore
Village)) v2[visitor 2] -- 'Can See' --> mnt((Mountain
Village)) v3[visitor 3] -- 'Can See' --> mnt((Mountain
Village)) sea --> app{Application} mnt --> app

Visitor 1 can ‘see’ the seashore village but not the mountain village. Similarly, visitor 2 and visitor 3 can ‘see’ the mountain village but not the seashore village. This structure also simplifies instantiation and garbage collection: if the seashore village turns off, visitor 1 will turn off as well, but these events will not affect the mountain village nor the visitors 2 and 3.

Starting containers and listening for events

We are now going to describe how containers are started and how we can listen for container events.

In the large container tree figure above, the 3 top most containers include a class highlighted in blue. AppliControl in APPLICATION, VillageControl in VILLAGE and VisitorControl in VISITOR. These classes extend the abstract class AppController in foundationj-wiring

An AppController has 2 important properties:

  1. At runtime, it is injected with the ScopeManager instance which exposes methods to list active containers and to start or stop containers using their name and runtime identifiers.
  2. It implements ScopeEventListener and is automatically registered to listen for all scopes events. The extending class can thus intercept scopes when they are about to start/stop or when they have started/stopped.

Here is an example, imagine the following sequence of events in our application:

sequenceDiagram participant APPLICATION participant VILLAGE participant VISITOR_1 participant VISITOR_2 activate APPLICATION APPLICATION -->>+ VILLAGE : STARTS VILLAGE -->>+ VISITOR_1 : STARTS Note right of VILLAGE: 1 Visitor Active... Note right of VISITOR_1: Does some
long activities... VILLAGE -->>+ VISITOR_2 : STARTS Note right of VILLAGE: 2 Visitors active... Note right of VISITOR_2: Does some
brief activities... VILLAGE -->> VISITOR_2 : INTERCEPT STOP deactivate VISITOR_2 Note right of VILLAGE: 1 Visitor left... VILLAGE -->> VISITOR_1 : INTERCEPT STOP deactivate VISITOR_1 Note right of VILLAGE: No Visitor left... APPLICATION -->> VILLAGE : STOPS deactivate VILLAGE Note right of APPLICATION: Stopping Application deactivate APPLICATION

To make this work, one AppController class is required per scope. We will give the code for the VILLAGE AppController: VillageControl.class

Lets say that once a user has chosen a village to visit, a new page appears where he/she can enter her name and start the visit. This page would be provided by a VillageUI in the VILLAGE scope (not shown in the tree figure):

	@Modular
	@Scope(name="VISITOR", parent="APPLICATION", policy=ScopePolicy.SIBLING)
	public interface VillageUI{
		// Marker interface for the UI
	}	

When the user has entered her/his name it fires an event to registered listeners. Here is the listener interface:

	@Modular
	@SameScopeAs(VillageUI.class) // As the name of this annotation suggests... :)
	public interface VillageUIListener{		
		public void visitSessionRequested(String name);		
	}	

One of these listeners is our VillageControl:

	@Module
	public class VillageControl extends AppController implements VillageUIListener{ // Obtains the Scope from the interface
		
		// Just keeps track of active visitors in the village 
		private final List<String> activeVisitors = new ArrayList<>(); 
		
		// Constructor
		public VillageControl(ScopeManager mgr, SomeUI ui){
			super(mgr) // Registers this instance as a ScopeEventListener
			ui.registerListener(this); // Registers this instance as a VillageUIListener 
		}
		
		...		

We can implement listener method so that we start a new VISITOR instance when we receive the name of the visitor

	
		//------------------------------------------//
		//          VillageUIListener Method        //
		//------------------------------------------//
		
		@Override
		public void visitRequested(String name){
			
			final RuntimeScope villageScope = scopeMgr.getScope(this); // Obtain a ref to our own container	
			// First we create a new container instance
			// We need to provide the Scope name, an id (VISITOR has a SIBLING policy so multiple instances can coexist)
			// and the last argument is the parent scope		
			final RuntimeScope visitorScope = scopeMgr.newScopeInstance("VISITOR", name, villageScope);
			// Then we can start the container
			scopeMgr.startScope(visitorScope);
		
		};

As you can see, starting a container only requires knowledge about the name of the scope we would like to start.

Now we can have a look at the methods from the ScopeEventListener interface:

		//------------------------------------------//
		//         ScopeEventListener Method        //
		//------------------------------------------//
		
		@Override
		public void beforeStop(RuntimeScope willStop){
			if(willStop.parent().equals(ourScope)){
			// do something ...
			}
		};

		@Override
		public void afterStop(RuntimeScope hasStopped){
			if(hasStopped.parent().equals(ourScope)){
				// remove the scope id from the list of activeVisitors
				activeVisitors.remove(hasStopped.id());
			}
		};

		@Override
		public void beforeStart(RuntimeScope willStart){
			if(willStart.parent().equals(ourScope)){
				// do something
			}
		};

		@Override
		public void afterStart(RuntimeScope hasStarted){
			if(hasStarted.parent().equals(ourScope)){
				// Add the scope id to the list of activeVisitors
				activeVisitors.add(hasStopped.id());
			}
		};
	
	}

These methods will be called for any of the RuntimeScope (container) present in the application. So if necessary, it is possible to intercept events for containers that have not been started by our class.

NB : The AppControllers are the only classes that need to obtain a reference to the container since they control the containers that need to be started or stopped. All other components should obtain their dependencies via construction injection as we explained in the first section of this page

@ScopeOption

Sometimes it may be useful to provide a stateful object to the container we create. In our example, when a visitor enters a shop, the VisitorControl would start a new SHOP_VIEW instance. This view however would need to determine which Shop in particular it is supposed to provide a view for.
One way would be for the DetailedShopUI class to depend on both the visitor’s Locator and the village’s VillageModel, then figure out which Shop resides at the current location of the visitor. This sounds a bit convoluted. An alternative is to use @ScopeOption to specify that a specific instance needs to be provided upon instantiation:

	
	private final Shop shop;
	
	public DetailedShopUI(@ScopeOption Shop){
		this.shop = shop;
	}
	

Then when the VisitorControl start a new SHOP_VIEW container, it needs to provide a specific Shop instance:


	// trigger method
	public void visitorHasEnteredShop(Shop shop){
		[...]
		scopeMgr.startScope(shopViewScope, shop); // we add the instance when we start the scope	
	}

@Service

The last annotation that we are going to mention in this page is the @Service annotation.

It may occur that a component is required in multiple containers but that it should not be a parent singleton as it would maintain some state specific to the container where it is used. For example, the MAP_VIEW and SHOP_VIEW containers may depend on some sort of rendering engine to display their content (not shown in the tree figure).

The declaration of the service would look something like this:


	@Service	// no need for @Scope of course since it is not bound to any particular Scope.
	public interface Scene3D {
		[...]
	}

And the consumer code:

	@Module
	public class DetailedShopUI{ 
		// Constructor declares the dependency as usual
		public DetailedShopUI(Scene3D scene){
			[...]
		}
	
	}

The alternative to the service described here would be to use a factory pattern: A Scene3DFactory could be added to the parent container and children would use the factory to create a new 3DScene on demand. Two advantage of the @Service system however are 1) that it allows to place the component in the container where it is actually used and 2) that a service can be obtained by containers which are located in disconnected trees. In fact PickCells uses this pattern.

Last Note

foundationj specifies other annotations which have not been listed here, please have a look at the foundationj-wiring repository, the Readme file contains a summary table of available annotations in foundationj and their purpose.