Azure Functions Best Practice — Reuse Client Instances, In Two Different Ways

This post was first published at Medium

Azure Functions is an event-driven serverless compute service available on Microsoft Azure Cloud. You can create a new Azure Functions app very easily in your IDE such as Visual Studio 2019, Visual Studio Code, Rider, etc. Cos I’m using Rider on my Mac, so I create a new Azure Functions solution and add a new function to the solution in Rider.

There are a static class called AzureTipsFunction and a static method called RunAsync in our function. (Actually, support for dependency injection begins with Azure Functions 2.x. So those IDE should use another template to generate the code).

You can easily run it locally or publish it to the Azure cloud to make it accessible to more people.

Although it is easy to develop an Azure Functions app, if you are not careful, you will also encounter serious problems. For example, you should keep in mind that functions in an Azure Function app share connections, such as HTTP connections, database connections, etc. And many libraries provide abstractions of external resources and those libraries manage their own connections to the resource, such as HttpClient, QueueClient, etc.

So, if your function code looks like this, it will create a new HTTP client instance every time the function is invoked.

public static class AzureTipsFunction
{
    [FunctionName("AzureTipsFunction")]
    public static async Task<IActionResult> RunAsync(
        [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]
        HttpRequest req, ILogger log)
    {
        log.LogInformation("C# HTTP trigger function processed a request.");
        
        using (var client = new HttpClient())
        {
            var client = new HttpClient();
            var response = await client.GetAsync("http://jiadongchen.com");
            ...
            ...
        }
    }
}

And you may encounter Host thresholds exceeded: Connections exception or some other performance issues during the running of your Functions app.

So how to fix this issue? This is the topic we will discuss in this post, namely “reuse client instances in your Azure Functions app”. And I’ll introduce two ways to implement instance reuse in an Azure Functions app.

# 1 Static Clients

Instead of creating a new HttpClient instance(and other clients) every time a function is invoked, you should use a single HttpClient instance for all the calls.

And the easiest way to implement it in Azure Functions is to declare the HttpClient as static outside of your Function’s Run code.

  // Create a single, static HttpClient
  private static HttpClient httpClient = new HttpClient();
  public static async Task Run(string input)
  {
      var response = await httpClient.GetAsync("http://jiadongchen.com/");
      // Rest of function
  }

In this way, every function invocation will reuse the same instance of HttpClient and it’s very easy to implement.

However, with the increasing amount of code, this method is not easy to maintain. Of course you can put the declaration of the HttpClient in an helper class, but it’s still difficult to maintain. Imagine another programmer doesn’t know the existence of this helper class…

# 2 Use dependency injection in .NET Azure Functions

Therefore, I prefer the second way.

As I mentioned at the beginning of this post, support for dependency injection begins with Azure Functions 2.x. Therefore, instead of using static Function class/Method, we can add a Startup class with the FunctionsStartup assembly attribute to register services we need, just like other ASP .NET projects. Then in order to make our dependencies available in a function, we should use constructor injection.

Then the code will become as follows.

  [assembly: FunctionsStartup(typeof(MyNamespace.Startup))]
  namespace MyNamespace
  {
      public class Startup : FunctionsStartup
      {
          public override void Configure(IFunctionsHostBuilder builder)
          {
              builder.Services.AddHttpClient();
              //builder.Services.AddSingleton<IMyService>((s) => {
              //    return new MyService();
              //});
              builder.Services.AddSingleton<ILoggerProvider, MyLoggerProvider>();
          }
      }
  }

We register the HttpClient in the Startup class.

  public class AzureTipsFunction
  {
      private readonly HttpClient _http;
      public AzureTipsFunction(HttpClient httpClient)
      {
          _http = httpClient;
      }
      [FunctionName("AzureTipsFunction")]
      public async Task<IActionResult> RunAsync(
          [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]
          HttpRequest req, ILogger log)
      {
          ...
      }
  }

And we removed the static modifier of the AzureTipsFunction class and add a constructor to the class to inject the HttpClient dependency into this HTTP-triggered function.

I personally think this is a more elegant way to implement client instances reuse in an Azure Functions app!

Ok, the above are two ways to implement “reuse client instances in your Azure Functions app” that I want to share with you in this post, and you can find more useful links below!

https://docs.microsoft.com/en-us/azure/azure-functions/manage-connections?WT.mc_id=DT-MVP-5001664

https://docs.microsoft.com/en-us/azure/architecture/antipatterns/improper-instantiation/?WT.mc_id=DT-MVP-5001664

https://docs.microsoft.com/en-us/azure/azure-functions/functions-dotnet-dependency-injection?WT.mc_id=DT-MVP-5001664


Thanks for reading and hope it’s helpful. Have a chat at https://www.linkedin.com/in/chenjd/


Subscribe To Jiadong Chen's Blog

Avatar
Jiadong Chen
Cloud Architect/Senior Developer

Cloud Architect at Company-X | Microsoft MVP, MCT | Azure Certified Solutions Architect & Cybersecurity Architect Expert | Member of .NET Foundation | Packt Author ㅣ Opinions = my own.

comments powered by Disqus

Related