Learn about asynchronous processing with Dagger by PPAP

At the previous article, I introduce the DI function in the Dagger.
PPAPで学ぶDaggerによるDI - きしだのはてな(Japanese)
Dagger is known as a DI container, but it is not known that can be used as an asynchronous framework.
There had been the function, but there was not a document about it. nowadays there is a document but we can not notice it can be used for an asynchronous process.
https://google.github.io/dagger/producers.html


At a server side, already they may have used DI containers such as Springframework or CDI.
Moreover the DI on Dagger is not enough for a server side, it can not be a candidate often.
But in the case of using as asynchronous processing framework, it is useful even with SpringFramework or CDI.

Define ExecutorModule

For an asynchronous processing, the module is needed to define an Executor to manage threads.
And @Production annotation is needed here.

@Module
public class ExecutorModule {
    static ExecutorService service;
    
    @Provides
    @Production
    static Executor executor() {
        return service == null 
            ? service = Executors.newCachedThreadPool() 
            : service;
    }
}

Defina a call graph

Define the call dependencies as a graph.
It feels like to make DI dependencies to call dependencies as is.
But the defferent annotations are used as bellow.

DI Async
@Module @ProducerModule
@Component @ProductionComponent
@Provides @Produces


It is needed to add the Executor management module into 'modules' at '@ProductionComponent'.
It is good that users can receive the object directly even if produce method returns 'ListenableFuture'.

@ProducerModule
public class AsyncPiko {

    @ProductionComponent(modules = {AsyncPiko.class, ExecutorModule.class})
    interface Taro {
        ListenableFuture<ApplePen> applePen();
    }
 
    @Produces
    public ListenableFuture<Pen> iHaveAPen() {
        return task("pen", Pen::new, 100);
    }
    
    @Produces
    public ListenableFuture<Apple> iHaveAnApple() {
        return task("apple", Apple::new, 200);
    }
    
    @Produces
    public ListenableFuture<ApplePen> applePen(Pen pen, Apple apple) {
        return task("applepen", () -> new ApplePen(apple, pen), 100);
    }
    
    private static <T> ListenableFuture<T> task(String name, Supplier<T> supplier, long millis) {
        ListenableFutureTask<T> task = ListenableFutureTask.create(() -> {
            System.out.println("start " + name);
            try {
                Thread.sleep(millis);
            } catch (InterruptedException ignored) {
            }
            T result = supplier.get();
            System.out.println("finish " + name);

            return result;
        });
        task.run();
        return task;
    }
}

There is a method definition named 'task' to process 'ListenableFuture' and brings parallel feeling, addition inserting a wait and logging.

Execution

Execution likes the case of a DI. Since there is solely static method '@Provides', it is not needed to pass any instance to the Dagger 'builder'.

public class AsyncPPAP {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        AsyncPiko.Taro pikotaro = DaggerAsyncPiko_Taro.builder()
                .asyncPiko(new AsyncPiko())
                .build();
        ListenableFuture<ApplePen> applePen = pikotaro.applePen();
        System.out.println("start ppap");
        System.out.println(applePen.get());
        System.out.println("finish ppap");
        
        ExecutorModule.service.shutdown();
    }
}


Result will be like this.

We can notice that 'pen' and 'apple' is processed in parallel, after finishing both, the 'applePen' is started.
Dagger manages such parallel dependencies.

Handle a collision of same type objects

Using Dagger for asynchronous processing, there are many situations to handle same type objects, as a result, it is not enough to resolve dependencies by type.
For example, make it as an asynchronous process that 'penPineapple' and 'penPineappleApplePen' return String.

    @Produces
    public ListenableFuture<String> penPineapple(Pen pen) {
        return task("penPineapple", () -> pen + "パイナッポー", 200);
    }
    
    @Produces
    public ListenableFuture<String> penPineappleApplePen(ApplePen applePen, String penPineapple) {
        return task("penPineappleApplePen", () -> penPineapple + applePen, 50);
    }


Then add a process to return String into @ProductionComponent.

    @ProductionComponent(modules = {AsyncPiko.class, ExecutorModule.class})
    interface Taro {
        ListenableFuture<ApplePen> applePen();
        ListenableFuture<String> ppap();
    }    


When do so, some error will occur.

It is good point of the Dagger to point out an insufficiency of a dependency at a compile time.


At this case, to define an annotation with '@Qualifier'.

    @Qualifier
    @Retention(RetentionPolicy.RUNTIME)
    @interface PenPineappleApplePen{}


Append this annotation at both user side and provider side.

    @Produces
    @PenPineappleApplePen
    public ListenableFuture<String> penPineappleApplePen(ApplePen applePen, String penPineapple) {
        return task("penPineappleApplePen", () -> penPineapple + applePen, 50);
    }
    @ProductionComponent(modules = {AsyncPiko.class, ExecutorModule.class})
    interface Taro {
        @PenPineappleApplePen
        ListenableFuture<String> ppap();
    }    


Finally, now uneventfully it can be compiled successfully and execute.


The full sources are on Github.
https://github.com/kishida/dagger_sample/tree/async