Click here to Skip to main content
15,891,204 members
Articles / Hosted Services / Azure
Article

Azure Cosmos DB + Functions Cookbook— static client

27 Dec 2018CPOL 8.2K   4  
Azure Cosmos DB is Microsoft’s globally distributed multi-model database, when we team it up with Azure Functions, Azure’s serverless compute service, the result is the ideal database for the serverless era.

This article is in the Product Showcase section for our sponsors at CodeProject. These articles are intended to provide you with information on products and services that we consider useful and of value to developers.

Preface

By definition, synergy happens when the interaction between two elements produces an effect greater than the individual elements’ contribution.

Azure Cosmos DB is Microsoft’s globally distributed multi-model database, when we team it up with Azure Functions, Azure’s serverless compute service, the result is the ideal database for the serverless era.

Image 1

In this series of posts I’ll explore different recipes, gotchas and examples of this integration targeting specific scenarios and act as building blocks for your architecture.

Using and scaling

With Azure Functions, we can create small pieces of code that will run based on events (time, HTTP calls, message queues, database operations, to name a few) and can scale independently to adjust to your computational needs. While we technically do not see nor worry about the servers hosting our Functions (it’s serverless after all!), there is a Runtime (or ScriptHost) that maintains the memory space (besides other things) where our Functions run.

When working with Azure Cosmos DB, we might have seen code that uses the using statement with the DocumentClient (after all, it implements the IDisposable interface) so we might be tempted to do this in our Function’s code:

#r "Microsoft.Azure.Documents.Client"
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client;
using System.Net;

private static string endpointUrl = ConfigurationManager.AppSettings["cosmosDBAccountEndpoint"]; 
private static string authorizationKey = ConfigurationManager.AppSettings["cosmosDBAccountKey"]; 

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
    // Avoid doing this!
    using (DocumentClient client = new DocumentClient(new Uri(endpointUrl), authorizationKey)){ 
        string id = req.GetQueryNameValuePairs()
            .FirstOrDefault(q => string.Compare(q.Key, "id", true) == 0)
            .Value;
        if (string.IsNullOrEmpty(id)){
          return req.CreateResponse(HttpStatusCode.BadRequest);
        }

        Uri documentUri = UriFactory.CreateDocumentUri("name of database","name of collection",id);
        Document doc = await client.ReadDocumentAsync(documentUri);

        if (doc == null){
          return req.CreateResponse(HttpStatusCode.NotFound);
        }

        return req.CreateResponse(HttpStatusCode.OK, doc);
    }
}

The problem with this approach is that it is creating an instance of the DocumentClient in each execution. We all know the woes of this approach for the HttpClient (and if you don’t, please read it right after this article!), and it has the exact same effect here: If the Function is getting a high volume of triggers, we not only will be penalizing the performance of our database calls with the initialization overhead but the memory consumption will raise and we might even incur in socket exhaustion scenarios.

The static client

One of the first and easiest-to-achieve performance improvement we can implement when working with Azure Cosmos DB is to use a single DocumentClient instance for all the service calls (see the full performance article for more). When we are building applications, we could achieve this by Depedency Injection frameworks to maintain a Singleton instance, but how can we achieve this in our Functions’ code? Turns out it’s pretty easy! Just declare the DocumentClient as static outside of your Function’s Run code:

#r "Microsoft.Azure.Documents.Client"
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client;
using System.Net;
using System.Configuration;

private static string endpointUrl = ConfigurationManager.AppSettings["cosmosDBAccountEndpoint"]; 
private static string authorizationKey = ConfigurationManager.AppSettings["cosmosDBAccountKey"]; 
private static DocumentClient client = new DocumentClient(new Uri(endpointUrl), authorizationKey);

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
    string id = req.GetQueryNameValuePairs()
        .FirstOrDefault(q => string.Compare(q.Key, "id", true) == 0)
        .Value;
    if (string.IsNullOrEmpty(id)){
      return req.CreateResponse(HttpStatusCode.BadRequest);
    }
  
    Uri documentUri = UriFactory.CreateDocumentUri("name of database","name of collection",id);
    Document doc = await client.ReadDocumentAsync(documentUri);

    if (doc == null){
      return req.CreateResponse(HttpStatusCode.NotFound);
    }
  
    return req.CreateResponse(HttpStatusCode.OK, doc);
}

For security purposes, the code is reading the Azure Cosmos DB Endpoint and Key from the Application Settings. Alternatively you could also use Azure Key Vault to store and retrieve this information. For more information, see my other related article.

This will effectively maintain one instance of the DocumentClient for all your Function’s executions within the same ScriptHost (instance) and improve the overall performance by reusing the same client for all service calls.

In an scenario where your Function App has multiple instances, each instance will maintain its own static client (it will not be shared among instances).

Stay tuned for more recipes!

Other posts in this series:

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
Software Engineer @ Microsoft Azure Cosmos DB. Your knowledge is as valuable as your ability to share it.

Comments and Discussions

 
-- There are no messages in this forum --