Getting Started with DocumentDB and Azure Functions

Introduction
Serverless computing is another buzzwords being passed around at the moment. Instead of setting up web servers and database servers you create everything in the cloud, you don’t even need to use a development environment as you can enter everything from you browser.

I can be quite cynical at times when “the next big thing” is being talked about, but this is one (along with bots and AI) is one I can really get behind. Several real world scenarios I have faced recently have been easily solved using these or their Amazon Web Services equivalent. I will share an example below:

I was faced with coming up with a solution that coincidentally involved a bot at one stage of the application. We needed to be able to get a small amount of information from about the current user that would allow us to direct information to the correct web services. I needed to make it as lightweight as possible as due to the complexity of components outside of my control, slow performance was a concern. Only a week or two earlier I had created my first Alexa Skill using two parts of AWS; DynamoDB their NoSQL database and Lambda which is their web functions. The speed and reduced overhead that these provided made me think that they would be the solution. Most of my team are all .Net developers so I decided to use Azure for this specific project, which meant using DocumentDB and some Azure Functions.

In this post I am going to go through the basics process of creating a DocumentDB database and then four functions to replicate the basic CRUD commands.

DocumentDB
DocumentDB is a NoSQL database, which to be honest is something I have heard for a while, but only really paid attention too recently. To me it was SQL that made databases, except for that one week where I did the old Java Developer certification and you had to write an old fashioned application!

Within a DocumentDB database you save Documents which are JSON objects. This makes them a great fit if you want to be able be used by a WebAPI service.

We are going to create a new DocumentDB so within the Azure Portal we add a new item and search for DocumentDB. The initial creation wizard is very simple, so enter a unique ID and the other settings here. These are all pretty standard across Azure. This wizard does give you the option to select DocumentDB or MongoDB, this is really aimed at users who are already using MongoDB and intending to migrate over so we are going to leave it as the default of DocumentDB.

CreateDocumentDB

Once we select create it will take a minute to provision and deploy this for us.

InitiallyDeployedSelect Add Database and then complete the extensive wizard for this, just enter a name!

DB

We now need to add a collection for this database. Be warned that these are billable so if you are not using this for production purposes, reduce the RTU’s to the lowest possible (400 as of writing this).

AddCol1AddCol2With that done, we have our DocumentDB setup with all that is needed. We will come back later to get the connection details, but we shouldn’t need to carry out any further config.

Azure Function

Azure Functions allow you to lightweight, serverless and event drive. It event says that on the Azure site. What that really means is, you don’t have to code much, they are fast, Azure handles the scaling and you can call them from pretty much anything. I’m not going to go into the setting up of webhooks or timer based triggers in this post, I’m going to just use basic Http triggers. But most of the principals are the same and Azure has a large stock of sample functions to help with whatever trigger you want to use.

First off we need to add a new Function App. This uses a pretty basic Azure creating wizard.

CreateFunctionAppOnce the Function App has finished deploying and you select it, the Function App will open in almost the width of the browser and you will be displayed a getting started page. You can choose your function type from here, or if you select New Function you will be able to see all the options available. I have used the drop down lists to refine the search and selected the HttpTrigger.

CreateFor the purposes of this demo we don’t need to worry about the Authorization level so I’m going to leave it at Function. The steps here are generic and we will use them throughout the rest of the document to build each of the four functions.

Once the function has been created you will see something similar to this:

capture10We wont be needing the boilerplate code, but before deleting it if you haven’t used functions before it may be worth scrolling to the bottom of the display and testing the simulator. By entering the value for name into the JSON you get a simple response from the function when you press Run.

capture13

Before we move on, you can check out the Integrate and Manage tabs sections selected from the left hand panel. Integrate allows you to select the types of trigger and output (including parameters) you want your function to have, along with settings such as Authentication. Most of this is set when you choose your function type, but you can make changes here if say you wanted to have an action triggered by a DocumentDB but with a request sent out via Http to another service.

capture11

From the manage tab you can enable/disable or delete your function.

capture12

With our function created, we can now write some code into the browser.

Adding a NuGet Package to your Function

This step will be used within all the functions in this post so I’m putting it at the top and getting it out of the way. From the Function UI you can not directly add NuGet packages. To connect to DocumentDB you need a NuGet package installed. Problem? Nope, not really. Although you cant add them normally, they are still easily supported. All you need to do is select View Files and add a new file called Project.json like shown below:

capture14

Inside that file you add the following json. If you need any other examples of this or are unsure of the settings for a particular NuGet package then you can just copy the detail from the Project.json file in any Visual Studio solution. That’s what I did!

{
  "frameworks": {
    "net46":{
      "dependencies": {
        "Microsoft.Azure.DocumentDB": "1.10.0"
      }
    }
   }
}

Function 1 – Create New Document

For our first function we are going to create a new document based on the json received in the body of the request. The full code is below and will be explained underneath.

using System.Net;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client;
using Newtonsoft.Json;

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
    string DB = "Users";
    string COLLECTION = "UsersCollection";
    string ENDPOINT = "<YOUR-ENDPOINT>";
    string KEY = "<YOUR-KEY>";

    dynamic data = await req.Content.ReadAsAsync<object>();
    string id = data?.Id;
    if (string.IsNullOrEmpty(id))
        return req.CreateResponse(HttpStatusCode.BadRequest, "Please pass an Id in the request body");
    DocumentClient client = new DocumentClient(new Uri(ENDPOINT), KEY);
    try
    {
        await client.ReadDocumentAsync(UriFactory.CreateDocumentUri(DB, COLLECTION, id));

        return req.CreateResponse(HttpStatusCode.Conflict, "This user already exists in the database.");
    }
    catch (DocumentClientException ex)
    {
        if (ex.StatusCode == HttpStatusCode.NotFound)
        {
            User u = new User();
            u.Id = data?.Id;
            u.JobTitle = data?.JobTitle;
            await client.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(DB, COLLECTION), u);

            return req.CreateResponse(HttpStatusCode.OK, "The following user was created successfully: " + id);
        }
        else
        {
            return req.CreateResponse(HttpStatusCode.BadRequest, "Please pass a name on the query string or in the request body");
        }
    }
}

public class User
{
    [JsonProperty(PropertyName = "id")]
    public string Id { get; set; }
    public string JobTitle { get; set; }
}

First I have created a few variables to contain the database details to connect to the DocumentDB that we created earlier. I then assign the Id value from the JSON in the body of the request to a dynamic to use later on. To simplify this post I am only checking the body for the Id. You could also check the header to allow developers to choose how they wanted to send requests.

Using the libraries from Microsoft.Azure.DocumentDB package we added using our Project.json file we first create a DocumentClient to allow us to access our database, then inside of a try/catch block we attempt to read the document. If this is successful then the document already exists so we return an error response to the user. If the document isn’t found then an exception is thrown. We check this to see if it is of the HttpStatusCode NotFound, if it is then we create the document using the CreateDocumentAsync method.

There is also a small User class at the end of the file for serializing the JSON.

The DocumentDB library has a UriFactory static class which you may have noticed we have used at several points. This class provides helpers for creating all the types of Uri needed for accessing the database.

Function 2 – Get a Document

Now that we have created a document in the database, we need to be able to retrieve it. We could create a function that allows us to do a complex search based, or one that returns only specific values. However for the purposes of this walkthrough, we are going to simply return the whole JSON document and allow the calling system to deal with anything else.

As with the previous example, the whole code is shown below and explained underneath.

using System.Net;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client;
using Newtonsoft.Json;

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
    string DB = "Users";
    string COLLECTION = "UsersCollection";
    string ENDPOINT = "<YOUR-ENDPOINT>";
    string KEY = "<YOUR-KEY>";

    dynamic data = await req.Content.ReadAsAsync<object>();
    string id = data?.Id;
    if (string.IsNullOrEmpty(id))
        return req.CreateResponse(HttpStatusCode.BadRequest, "Please pass an Id in the request body");
    DocumentClient client = new DocumentClient(new Uri(ENDPOINT), KEY);
    try
    {
        IQueryable<User> users = client.CreateDocumentQuery<User>(UriFactory.CreateDocumentCollectionUri(DB, COLLECTION)).Where(u => u.Id == id);
        User currentUser = new User();
        foreach(var u in users)
        {
            log.Info(u.Id);
            currentUser = u;
        }
        if (string.IsNullOrEmpty(currentUser.Id))
            return req.CreateResponse(HttpStatusCode.BadRequest, "Unable to access user within DB");
        string rtn = JsonConvert.SerializeObject(currentUser);

        return req.CreateResponse(HttpStatusCode.OK, $"{rtn}");
    }
    catch (DocumentClientException ex)
    {
        if (ex.StatusCode == HttpStatusCode.NotFound)
            return req.CreateResponse(HttpStatusCode.BadRequest, "This user does not exist in the database.");

        return req.CreateResponse(HttpStatusCode.BadRequest, $"An unknown error has occured. Message: {ex.Message}");
    }
}

public class User
{
    [JsonProperty(PropertyName = "id")]
    public string Id { get; set; }
    public string JobTitle { get; set; }
}

A fair amount of the code for this function is the same as the previous example. What I have done is in the try/catch block called the CreateDocumentQuery method on my DocumentClient. This will return an IQueryable of User objects. As I am trying to show a simple demonstration I have not implemented IEnumerable on by User class so am still using a foreach loop (not the cleanest code but allows the point to be made). As I am searching only by the Id which as the partition key on the DocumentDB must be unique, we can be sure of only a single value being returned.

We then use the SerializeObject method from Newtonsoft.Json’s JsonConvert class to create a string that is returned as the body of the response. This enables the calling system to retrieve the full JSON document from the database.

Function 3 – Update a Document

As we are using DocumentDB and just storing JSON objects, instead of updating an object directly we are going to replace the JSON object with an updated copy.

using System.Net;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client;
using Newtonsoft.Json;

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
    string DB = "Users";
    string COLLECTION = "UsersCollection";
    string ENDPOINT = "<YOUR-ENDPOINT>";
    string KEY = "<YOUR-KEY>";

    dynamic data = await req.Content.ReadAsAsync<object>();
    string id = data?.Id;
    string jobTitle = data?.JobTitle;
    if (string.IsNullOrEmpty(id))
        return req.CreateResponse(HttpStatusCode.BadRequest, "Please pass an Id in the request body");
    if (string.IsNullOrEmpty(jobTitle))
        return req.CreateResponse(HttpStatusCode.BadRequest, "No job title provided for update.");
    DocumentClient client = new DocumentClient(new Uri(ENDPOINT), KEY);
    try
    {
        IQueryable<User> users = client.CreateDocumentQuery<User>(UriFactory.CreateDocumentCollectionUri(DB, COLLECTION)).Where(u => u.Id == id);
        User userToUpdate = new User();
        foreach(var u in users)
        {
            userToUpdate = u;
        }
        if (string.IsNullOrEmpty(userToUpdate.Id))
            return req.CreateResponse(HttpStatusCode.BadRequest, "Unable to access user within DB");
        userToUpdate.JobTitle = jobTitle;
        await client.ReplaceDocumentAsync(UriFactory.CreateDocumentUri(DB, COLLECTION, id), userToUpdate);

        return req.CreateResponse(HttpStatusCode.OK, $"The job title for user {id} has been updated to {jobTitle}");
    }
    catch (DocumentClientException ex)
    {
        if (ex.StatusCode == HttpStatusCode.NotFound)
            return req.CreateResponse(HttpStatusCode.BadRequest, "This user does not exist in the database.");
        return req.CreateResponse(HttpStatusCode.BadRequest, $"An unknown error has occured. Message: {ex.Message}");
    }
}

public class User
{
    [JsonProperty(PropertyName = "id")]
    public string Id { get; set; }
    public string JobTitle { get; set; }
}

First we use the CreateDocumentQuery method like we did in our retrieve function. After checking that the document exists we then edit the document to include the new JobTitle. Finally we call the ReplaceDocumentAsync method on our DocumentClient with the parameters for the Document uri using the UriFactory class and the new JSON object to store in the DB.

Function 4 – Delete a Document

Our final function to create our basic CRUD functionality is to delete a Document.

using System.Net;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client;
using Newtonsoft.Json;

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
    string DB = "Users";
    string COLLECTION = "UsersCollection";
    string ENDPOINT = "<YOUR-ENDPOINT>";
    string KEY = "<YOUR-KEY>";

    dynamic data = await req.Content.ReadAsAsync<object>();
    string id = data?.Id;
    if (string.IsNullOrEmpty(id))
        return req.CreateResponse(HttpStatusCode.BadRequest, "Please pass an Id in the request body");
    DocumentClient client = new DocumentClient(new Uri(ENDPOINT), KEY);
    try
    {
        await client.ReadDocumentAsync(UriFactory.CreateDocumentUri(DB, COLLECTION, id));
        await client.DeleteDocumentAsync(UriFactory.CreateDocumentUri(DB, COLLECTION, id));

        return req.CreateResponse(HttpStatusCode.OK, $"The following user has been deleted from the database : {id}");
    }
    catch (DocumentClientException ex)
    {
        if (ex.StatusCode == HttpStatusCode.NotFound)
            return req.CreateResponse(HttpStatusCode.BadRequest, "This user does not exist in the database.");
        return req.CreateResponse(HttpStatusCode.BadRequest, $"An unknown error has occured. Message: {ex.Message}");
    }
}

Unlike our previous functions, the delete function does not need to know about the User object so this is not included. Our fist step after checking the request has an Id, is to try and read the document. This allows us to confirm that a document with the provided Id does indeed exist. If it does we then call DeleteDocumentAsync on our DocumentClient using a DocumentUri created by the UriFactory.

Testing

Now that you have functions for creating, retrieving, updating and deleting you can test these against your DocumentDB database. At the bottom of the browser page for the function you can update the JSON as shown below and when you press Run you will see in the Output panel the text we entered for the return message.

Test1

Now you can test the retrieve operation by going to that function and changing the JSON request to this value. You will then see the JSON string in the return.

capture16

Final Words

This post has covered a fairly simple example, that said we do now have all the steps needed for accessing and managing data in our database. In future posts I will be incorporating Azure Functions and DocumentDB into a bot that can store data on behalf of a user. If you have any questions or comments, please add them below.

Author: Dave Green

Software developer focusing on Microsoft Technologies, MCSD in Windows Store Apps, full time developer at a real job, part time writer and terrible golf player. Follow on twitter @IntelligentLabs

Leave a Reply

Your email address will not be published. Required fields are marked *