Re-use service instances by Implementing Dependency Injection in Azure Functions

Tobias Zimmergren
Tobias Zimmergren
💡TIP: Check out the guidance for building sustainable Azure workloads! 🌿

Imagine you have a Function App that has multiple Functions deployed. It's a common scenario, and depending on how you've built the architecture for the solution, it can come in handy to use Dependency Injection to instantiate a service once when the Function App (host) starts up, and re-use that object across all executions of your Functions inside this Function App.

To paint a picture of what this means, I can refer to one of my spare-time project scenarios.

I have a Function App on a premium tier with 8 Functions that are a mix between Timer Triggers and Queue Triggers. This solution processes hundreds of million queue items per month, by default.

Every function app has a core set of logical services I need for specific scenarios, like custom loggers, database connections, and APIs - these usually take half a second to initiate because they wire up the connections to their endpoints.

At this scale, I can't afford the time or resources to build up all my services required in every single execution of a Function. It would take too long, and there's no need to re-create a brand new instance to a database a few million times per day if I can re-use the connection securely. Instead, I can rely on Dependency Injection (DI) to wire up my base services once when the Function App (host) starts, and then use that instance when my queue-triggered Function runs. Removing the need to spin up a new connection or instance of that service repeatedly is great, and I can instead make use of the singleton instance across all the executions.

TLDR:

  • Before DI: 2500ms to execute (avg).
  • After DI: 1800ms to execute (avg).
  • Reducing execution time by ~700ms per function execution.
  • Reducing stress on the endpoints (Storage/Databases, APIs, ..).
  • At scale: I "save" 81 days worth of executions per month per every 10M runs

Here are the numbers:

Azure Functions with Dependency Injection, saving me a lot of execution time.

I also like charts, so I'll throw this visual comparison into the mix:

Comparison between using Dependency Injection, and not, in Azure Functions.

This picture illustrates (1) not implementing my Dependency Injection, and (2) after I've implemented it.

Perhaps you're thinking "How can you save more days than there are in a month?", you're right - the Functions are set up with multiple instances (scale-out), and they execute multiple messages in parallel, so the times are per-function if they were to be executed synchronously.

I thought this would be an ideal way to visualize the difference between optimized and non-optimized code, and that a simple move of business logic from each Function to the startup helped save a lot of time (and in the end, credits/money on my bill). I have scaled down my instance count by 2 since I implemented this change, and haven't had to scale back out. That's a win for me! 😎🚀

How to implement Dependency Injection with Azure Functions

This is very well documented on the Microsoft Docs website already, so I'll just share the core steps of my own setup, with pseudo code as the actual business logic doesn't help to understand the sample code.

Step 0. Who can make use of this?

  • Support for dependency injection begins with Azure Functions 2.x.
  • Dependency injection in Azure Functions is built on the .NET Core Dependency Injection features.

Step 1. Create a FunctionStartup class

Start by creating a new class that inherits from FunctionsStartup. This class is available with the following nuget:

It lives in this namespace:

  • Microsoft.Azure.Functions.Extensions.DependencyInjection

The built-in Microsoft.Extensions.DependencyInjection should already be available (which enables you to use the .AddSingleton method.

This is what the class can look like, where you override the Configure method:

using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using System;

[assembly: FunctionsStartup(typeof(FuncStartup))]
namespace CMM.Functions
{
    public class FuncStartup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            if (builder == null)
                throw new ArgumentNullException(nameof(builder));

            // TODO: Wire up your services here. 

            // Example, inject a service with a one-liner.
            builder.Services.AddSingleton<IMyServiceTypeHere>(new UsefulService());

            // Example, inject a service with with additional logic.
            builder.Services.AddSingleton<IAzureBaseConfig>((_) =>
            {
                FunctionHelpers funcHelp = new FunctionHelpers();
                funcHelp.Init().Wait();
                return funcHelp.WireUpBase("CMM.Functions", null).Result;
            });
        }
    }
}

In the code sample, I've removed any custom logic and tried to make it as plain and simple as possible to understand. You inject services as Singletons, which means they're available by all Function instances.

Step 2. Inject your services in the Function apps

Now that we've injected our services as singletons, we can start making use of them from our Functions.

We can accomplish this the usual DI-way, which is by adding a constructor to our Function, and then reference the services from there - I'm setting them as readonly variables locally in my Function, so I can easily reference them from my Run method:

public class ExchangesUpdateApp
{
    private readonly IAzureBaseConfig _config;
    private readonly IMyServiceTypeHere _myUsefulService;
    
    public ExchangesUpdateApp(IAzureBaseConfig config, IMyServiceTypeHere usefulService)
    {
        _config = config;
        _myUsefulService = usefulService;
    }

    [FunctionName("ExchangesUpdateApp")]
    public async Task Run(
      [QueueTrigger("exchangesprocessing")]exchangeQueueItem, 
      ILogger log)
    {
        // Business logic redacted for the sake of demo clarity.

        // From here, we can now use the _config instance, 
        //  which are injected using DI.
        // As such, they don't need to be re-instantiated here, 
        //  and we can save a lot of wiring and plumbing.
        
        // _config.
        // _myUsefulService.

        // ...
    }
}

That's it - you're now using the same instance of the injected service from all executed functions, instead of re-creating the logic every time you're executing.

For more insights and more on this topic, refer to the links at the end of this post.

Troubleshooting

One issue I encountered, which is definitely not the first time, but very much a user-error on my side:

Microsoft.Extensions.DependencyInjection.Abstractions: Unable to resolve service for type while attempting to activate 'News.Functions.Apps.SchedulerApp'

I tried to inject a type of a Class, rather than the Interface. Solution was to use the interface in the constructor, not the instance class. Apparently, I am not alone:

Admittedly, I've done that before, too. 🤦‍♂️😂

Implementing Dependency Injection is fairly easy, but not something I see a lot in demos and talks around the sphere. I wanted to share my thoughts and insights on using this approach, and perhaps there's someone who'd be keen on exploring the same approach.

For my projects, I reduced init-times of every single execution by quite a lot, as most of the heavy lifting was wiring up the services I'm using. This will of course differ for your use cases depending on what your Functions are doing.

Microsoft have great details about how to build Functions with DI.

AzureAzure Functions.NET

Tobias Zimmergren Twitter

Hi, I'm Tobias! 👋 I write about Microsoft Azure, security, cybersecurity, compliance, cloud architecture, Microsoft 365, and general tech!

Reactions and mentions


Hi, I'm Tobias 👋

Tobias Zimmergren profile picture

Find out more about me.

Recent comments

Mastodon