Dependency injection with AWS Lambdas in java

Photo by Chris Ried on Unsplash

Dependency injection with AWS Lambdas in java

How to use Dagger to bring best practices to your Lambdas

Disclaimer: for those who wants to see the solution, the github is here

No need to present the AWS Lambdas ; those have been disruptive innovations and they made the app development process smoother and more flexible. In this article, I will expose a way to add dependency injection within your lambdas.

Why dependency injection with nanoservices ?

The question is fair. Indeed, as each function is independent from the other, why bother yourself adding a new level of abstraction and complexity ?

In fact, if you you want to follow the good practices of scalable code, some parts of your code is shared between functions. For instance, the services querying the database. Thus, your source code grows in complexity and becomes similar to a more « classic » web application, inviting you to follow the SOLID acronym.

Off course, depending on your needs and situation, it would be better to not do it but if you target the long run, it would be suicide to unfollow the good practices (exponential growth of maintenance costs…).

As said in the title, we will focus on the dependency inversion principle and one of its application : dependency injection. For production-ready applications, it would be better to rely on a framework and not implement its own container. For it, the java ecosystem have 3 frameworks available : Spring, Guice and Dagger.

Spring and Guice

In the java world, Spring is THE most-used solution. Proven, maintained at a constant pace, it became the standard. However, its ecosystem is too large (IOC, MVC…) for our needs so we’ll have to install a lot of useless frameworks just for dependency injection making the package size not optimal. Moreover, all the injection is done at runtime making your cold start even longer ! Needless to say that all the binding errors will be detected at runtime so when the lambda is launched. Thus, Spring is not a good choice.

On the other hand, Guice is another dependency injection framework created by Google. It a good alternative because :

  • Framework package size much lighter than Spring

  • No external configuration file to setup (no properties or xml files)

However, it wouldn’t feet our needs because like Spring all the injection is done at runtime.

Then, is there a good solution ? yes and it is Dagger !

Dagger ?

Dagger is a dependency injection framework developed by Square and forked by Google. It is widely used in the Android ecosystem. Its key points are :

  • Lightness : juste dependency injection

  • Use of java.inject annotation

  • Last but not least, the injection is done at compilation. No more binding errors at runtime ! Thus, all the injection is done at the packaging of our lambda bringing the cold start to its bare minimum.

Let’s deep dive a little.

Short overview of Dagger

Dagger is built around 2 concepts : Modules and Components.

A module is a container, represented by a class, where you declare your classes and define the dependency relationship between them. An example :

@Module
public class DaggerAwsLambdaModule {

  @Provides
  @Singleton
  public LambdaFacade lambdafacade(IAggregateService aggregateService)       {
    return new LambdaService(aggregateService);
  }

  @Provides
  @Singleton
  public IAggregateService aggregateService(IAService iaService, IBService ibService) {
    return new AggregateService(iaService, ibService);
  }

  @Provides
  @Singleton
  public IAService iaService() {
    return new AService();
  }

  @Provides
  @Singleton
  public IBService ibService() {
    return new BService();
  }

}

A component is an interface used as the entry point of the application. It also links the modules needed to make the application run. Here goes an example :

@Singleton
@Component(modules = {DaggerAwsLambdaModule.class})
public interface AwsLambdaComponent {
    LambdaFacade lambdaService();
}

From there, Dagger will generate factories from modules and Builders from components.

Use case

The example is quite simple. We have 3 services : AService, BService, AgregateService. Each one has a print function which returns the eponym letter. The AgregateService similarly returns « ABC ».

All the plue-value is at the LambdaDispatcher class. This is a dispatcher acting as an entry-point and allowing to :

  • Generate the component and the dependency tree

  • Forward the request to the related service

  • Bring an abstract lawyer splitting the business code from the infrastructure one (ie AWS and Dagger librairies) making the code more maintanable and testable.

Here is the implementation :

public class LambdaDispatcher {

  private final AwsLambdaComponent awsLambdaComponent;

  public LambdaDispatcher() {
        this.awsLambdaComponent = DaggerAwsLambdaComponent.builder().build();
    }

    public ApiGatewayResponse greet(LambdaRequest input, Context context) {
        context.getLogger().log("entering the function");
        LambdaResponse response = awsLambdaComponent.lambdaService().greet(input);
        return new ApiGatewayResponse(response.toString(), false, "200", Collections.emptyMap());
    }
}

And the component :

@Singleton
@Component(modules = {DaggerAwsLambdaModule.class})
public interface AwsLambdaComponent {
    LambdaFacade lambdaService();
}

Module :

@Module
public class DaggerAwsLambdaModule {

  @Provides
  @Singleton
  public LambdaFacade lambdafacade(IAggregateService aggregateService) {
    return new LambdaService(aggregateService);
  }

  @Provides
  @Singleton
  public IAggregateService aggregateService(IAService iaService, IBService ibService) {
    return new AggregateService(iaService, ibService);
  }

  @Provides
  @Singleton
  public IAService iaService() {
    return new AService();
  }

  @Provides
  @Singleton
  public IBService ibService() {
    return new BService();
  }

}

As an image worth 1000 words :

Here is the github.