Sakila: Dagger 2 Dependency Injection

REST web server and dependency injection

The code from this article is available here.

I described basic usage of Dagger 2 already in this article, now we need to implement dependency injection mechanism in the web application. Typical web application would have at least two layers, one for the web server itself and another for client request processing.

Spark Java web server internally use embedded Jetty web server. To setup and start the server we provide some central services like configurations, authentication, statistics etc. Those services are usually instantiated once per whole application.

For each client request a new thread is allocated for the whole duration until the request is processed. Each client request trigger a method defined in the router.

Depend on the design decisions but we usually want that each request will be processed by new object instance (an example is ActorResource in the picture). We will probably have multi threaded problems or simply data leaks from one user to another if we don’t create new instance each time the request is received.

Some objects needed in the typical request processing scenario have different lifecycle requirements, for example database transaction object must be the same for the whole duration of the started transaction but different for each user. There are times when we need two independent transactions in one service request for example. Transactions are span over multiple service objects for example.

On the other side when we do not require transaction (read only processing) we would be better of if we use first available connection allocated to us from the connection pool for the smallest amount of time possible.

As we see from the use cases above there are very different lifecycle scenarios and dependency injection must support them all.

Scopes

We will need at least two scopes for our web application. First scope is application level scope. In the dagger this scope is on by default. Each time we tag a class as a “@Singleton”, the object will be instantiated on the application level and all subsequent requests for this object will return the same instance. So the singleton representing an “application scope” by default. No need for specific scope definitions.

Classes without any scope annotations (no @Singleton or any other scope) are always provided with a new instance.

To manage injection on the application level we create ApplicationComponent interface and ApplicationModule class.

Application module class:

At the application level example we present next use cases:

  • creating an instance with the supplied constructor parameter (ConfigProperties service)
  • instantiate objects  from the external dependencies with provide method (Gson)
  • instantiate specific implementation for an interface (ResponseTransformer interface)
  • instantiate an String object with named annotation (using name as differentiator)

Request scope

To create “request scope” we write one annotation interface (“@interface”) one component (RequestComponent) and at least one module class (RequestModule). The component must have sub-component annotation.

To manage DI on the request scope level we create annotation type interface RequestScope , RequestComponent interface and RequestModule class.

Just to be clear I want to emphasize that each class annotated with the “@RequestScope”  annotation will be instantiated exactly once per created instance of RequestComponent class.  It means that scope annotations represent local singletons.

Module class:

We use localized singletons especially for the transaction and jooq data access support.

Provide methods are optional, we can decorate classes in the source code with the corresponding annotations.

Service classes

If we analyze the code in the consumer classes, it become ridiculously simple.  All externalized requirements are created by the dagger code almost hassle free.

In the ActorResource class for example we analyze received request,  extract parameters and start business logic. The transaction object is created on the request scope and pass down to all service objects in need to collaborate in the same database transaction.

In the ActorService class we receive all constructor parameters from the dagger automatically.

The class ActorService require two objects at the constructor: jooq DSLContext  class and ActorDao class.

DSLContext class is part of the Jooq data access library and is instantiated with the provider method “provideDSLContext”. It is annotated as @RequestScope it means the RequestComponent will keep single instance of it for the duration of one request cycle.

ActorDao object is also generated by Jooq library so we couldn’t tag it with scope annotation in the source (so we wrote the provideActorDao method in the request module).

Summary

Dagger calculate all dependencies in the compile time and generate the required code for the whole graph of dependencies and is able to instantiate appropriate objects at appropriate times really fast.

 

The code from this article is available here.

Other resources:

More about dagger scopes, sub-components .

 

Dependency injection with Dagger 2

Prepare project to use dagger DI in IntelliJ IDE

The IntelliJ IDEA Community version 2017.2.5 is used in this example.

Open project structure and add dagger as project dependency from Maven repository.

  • com.google.dagger:dagger:2.10
  • com.google.dagger:dagger-compiler:2.10

The dagger compiler has to be available only for development phase (annotation processing will generate code) so we added it as “Provided” dependency.

Enable annotation processing in the project settings and select content root for generated code.

If you wish to inspect generated code in the project tree it is wise to open “generated” folder manually and mark it as “Source root”.  After compiling the project tree will look like this:

Create simple service application

First we will create very simple application without DI. The service application will consist of one Application class starting everything up and with single Service class doing some work.

Implement dagger dependency injection

To start as simple as possible we will not change how the application is instantiated and started.  The dagger is initialized in the Application object and the only injected instance will be the instance of Service class.

Now we need to create  “Module” object where we implement “provide” method to provide new instance of the service class. The module class must have @Module annotation and factory methods must have @Provides annotations.

Now we need a “Component” object which is an interface from which dagger generate injector class.  The component must contain a @Component annotation with defined modules and inject function signature.

The service class was not changed because we do not inject anything there, the service has no external requirements, so the code stay exactly the same as before:

Connect everything together

In the Application constructor we initialize the dagger with the build() and inject() functions. The injection is achieved with the @Inject annotation in front of the local “service” member declaration.

If a dagger want to inject a class member it must be publicly accessible because it is not using any reflection (that’s good for speed and debugging!). But we could also initialize private variables with the use of constructors marked with annotation @Inject taking in all required parameters.

Extending our knowledge

@Inject on the constructor instead of provide method

Now we could explain that provideXXX methods in the modules are not the only way how to explain to dagger how to construct the objects … We can instead of the method simpy annotate constructor of the object with @Inject.

Because only one constructor can have the @Inject annotation the dagger know exactly how the object should be constructed. The special provideXXX method on the module is not needed anymore.

Try to add constructor to Service class annotated with @Inject and delete provideService() method from the “SimpleModule” class , rebuild the project and everything should work as before. You can observer generated classes because know the daggergenerate special factory object with construction mechanism.

The code after changes should look like:

and the module:

 

More about the topic

DI and the dagger tool has many features not covered in this article. You can find more about them here .