loading...

Advanced Serverless AWS Lambda Dependency Injection

gary_woodfine profile image Gary Woodfine Originally published at garywoodfine.com on ・6 min read

I have previously provided a simple example of how to introduce simple dependency injection and configuration to your Serverless Framework AWS Lambda, In this post, we’ll dive a little deeper and learn how to completely implement dependency injection and how to layout your Lambda Project.

The issue when creating projects using frameworks and templates is that it often guides developers into thinking that the only way too can implement solutions is by following the guidelines provided by the project template.

The truth is when it comes to the Serverless Framework the only thing that is of any real importance is the YAML file, which is primarily used to create Cloud Formation script to create and manage the AWS resources to be used by your Lambda and the deployment script. When it comes to the actual code and structure of your lambda project and the code it contains, it really doesn’t care.

When generating a new AWS Lambda project, the Serverless Framework generates only the simplest project possible. A project which has hardly any features that most .net developers are typically used too and it falls on to them to implement all the features they need.

In this post, I will walk you through how to implement full-blown Dependency Injection, Configuration and even make use of IHost functionality in your AWS Lambda projects. The only restriction you’ll face in AWS Lambda Projects is that at the time of writing you are limited to using .net core 2.1

GitHub logo garywoodfine / HelloConfiguration

AWS Lambda and .net core dependency injection

Hello Configuration

Source Code to support a series of blog posts to detailing how to use Dotnet Core Configuration and Dependency Injection in AWS Dotnet core lambdas

Donate

Hello Configuration is a FREE tutorial developed and supported by Threenine.co.uk

If you would like to make a donation to keep the developers stocked with coffee and pizza's would be greatly appreciated.

Donate via PayPal

threenine logo




Start a new AWS Lambda Project

If you haven’t used or unfamiliar with the Serverless Framework then I suggest you read Getting started with .NET Core and the Serverless Framework.it will guide you through installing, provide a basic understanding of Serverless Framework and generate a typical project. I will assume you already have this knowledge for the remainder of this tutorial.

Information

We will be continuing to develop on the code presented inSimple Dependency
Injection In AWS Lambda

Let's generate a new Serverless project

sls create -t  aws-csharp -p HelloConfiguration -n Threenine.ConfigTest

The reality is we could just delete the .net project created and start again, because the only things of use for us in the generated project is the serverless.yml , build.sh but we’ll work with what we’ve got!

We’re going to do a fit of refactoring here to tidy up the generated project to better suit our conventions etc. In a future post, I will provide details on how you can create your own custom Project Templates with the Serverless Framework so you don’t have to do this Boilerplate grunt work on every project you start.

In the first instance, we will want to rename our assembly to more closely match its purpose. When you’re engaging in Micro-services Development and developing many Lambdas you don’t really want 100’s of lambdas all named aws-csharp and a default namespace of AwsDotnetCsharp, it really doesn’t describe the component you’re developing and is counter-intuitive to the Philosophy of Software Design

The process to renaming your Assembly and setting the default namespace is as normal in whichever IDE or Text Editor you’re using. I predominantly use Jet Brains Rider – A fast & powerful cross-platform .NET IDE , so my process will be slightly different to most. However, there are few important edits to make to your serverless.yml to ensure you don’t implement breaking changes.


service: Hello-Configuration

provider:
  name: aws
  runtime: dotnetcore2.1

package:
  individually: true

functions:
  hello:
    handler: HelloConfiguration::Threenine.ConfigTest.Handler::Hello


    package:
      artifact: bin/release/netcoreapp2.1/hello.zip


You’ll notice I simply edit line 12 to represent the fact I have renamed my Assembly to HelloConfiguration and my Namespace for my project is now Threenine.ConfigTest. For the purpose of this Demo code, this will suffice, I just wanted to highlight that you can edit these project files to better suit your needs.

Add a StartUp class

In the sample code for Simple Dependency Injection In AWS Lambda we followed some pretty bad coding practices and created one class that did everything, which is not SOLID and could cause all sorts of maintenance nightmares so we do some refactoring on that class and split core functional components into their own classes. I always have the principles of Adaptive Code in mind when developing!

So lets add a new class and by convention we’ll call this StartUp.cs

We are going to slightly refactor our ILambdaConfiguration and LambdaConfiguration to make use of IConfigurationRoot which represents the root of an IConfiguration hierarchy

public interface  ILambdaConfiguration
    {
        IConfigurationRoot Configuration { get; }
    }

public class LambdaConfiguration : ILambdaConfiguration
    {
        public static IConfigurationRoot Configuration  => new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
            .Build();


        IConfigurationRoot ILambdaConfiguration.Configuration => Configuration;
    }

Our new startUp class is going to be the location where we wire up our dependency and configuration data that our application is going to use.

So before we actually do that, we’ll have to create our services and do some more refactoring of our application. In my example I deleted the Handler.cs file created a new directory named Functions and created a new class in that directory named Speak.cs

For the sake of demo, I created an additional Directory named Services and created an interface of ISpeakService and a class called SpeakService

public interface ISpeakService
    {
        string Greeting { get; }
    }



public class SpeakService : ISpeakService
    {
        private readonly Greeting _greeting;
        public SpeakService(IOptions<Greeting> hello)
        {
            _greeting = hello.Value;
        }

        public string Greeting => _greeting.Message;
    }

You’ll notice that the SpeakService with make use of the Options patternprovide a mechanism to validate configuration data . We also seem to using a class called Greeting, let's go ahead and create that class

[JsonObject("greeting")]
    public class Greeting
    {
        [JsonProperty("message")]
        public string Message { get; set; }
    }


We can now wire up our dependencies in our StartUp class

public class StartUp
    {
        public static IServiceCollection Container => ConfigureServices(LambdaConfiguration.Configuration);


        private static IServiceCollection ConfigureServices(IConfigurationRoot root)
        {

            var services = new ServiceCollection();

            //Wire up all your dependencies here
            services.Configure<Greeting>(options =>
                root.GetSection("greeting").Bind(options));

            services.AddTransient<ISpeakService, SpeakService>();

            return services;
        }
    }


In our Speak.cs class we’ll now make use of all our dependencies. You’ll notice that we will create 2 class constructors one of which will take in an IServiceProvider and the other will instantiate this class by initialising out StartUp class.

Our greet method will then Get a service we from our IOC container and use it. We make use of the ServiceProvider.GetService method to get the service object of the specified type.


public class Speak
    {
        private readonly IServiceProvider _serviceProvider;

        public Speak(IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
        }

        public Speak() : this(StartUp.Container.BuildServiceProvider())
        {
        }

        [LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))]
        public string Greet(ILambdaContext context)
        {
            var talk = _serviceProvider.GetService<ISpeakService>();

            return talk.Greeting;
        }
    }

We need to update our Serverless yaml to take into consideration our refactoring

service: Hello-Configuration

provider:
  name: aws
  runtime: dotnetcore2.1

package:
  individually: true

functions:
  hello:
    handler: HelloConfiguration::Threenine.ConfigTest.Functions.Speak::Greet


    package:
      artifact: bin/release/netcoreapp2.1/hello.zip


We will also modify our AppSettings file slightly to create a Greeting section

{
    "greeting" : {
        "message": "This is from the configuration"
    }
}


Build, Deploy and Invoke

We can now build our lambda


sudo ./build.sh

We can then deploy our Lambda


sls deploy

Then we can invoke it to see if everything works as expected


sls invoke -f hello

Summary

In this sample, we have enabled our lambda to make use of some great .net core features of Dependency Injection and Configuration. This will enable developers to de-clutter their lambda functions and help to organise their code and also enable the use of additional libraries.

Posted on by:

gary_woodfine profile

Gary Woodfine

@gary_woodfine

Remote Full Stack Web Engineer Specializing in the C#, .NET Core, Azure, AWS, JavaScript, Node.js.

Discussion

markdown guide