Easy real-time, event-driven and serverless with Azure

One of the most exciting parts of modern cloud platforms is serverless and the event driven architectures you can build with it. The reason that it’s so exciting is because it’s a double win, not only does it give you all the advantages of the cloud like easy elastic scale, isolation, easy deployment, low management etc but it’s also extremely easy to get started. We can also avoid the container and container management rabbit hole.

Azure Functions

Azure Functions is a serverless offering on the Azure platform. In a nutshell you create individual functions that you upload into the cloud and run individually. Here’s an example of a simple function that you can call via Http.

[FunctionName("SimpleFunction")]
public static async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
    ILogger log)
{
    log.LogInformation("C# HTTP trigger function processed a request.");

    string name = req.Query["name"];

    string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
    dynamic data = JsonConvert.DeserializeObject(requestBody);
    name = name ?? data?.name;

    return name != null
        ? (ActionResult)new OkObjectResult($"Hello, {name}")
        : new BadRequestObjectResult("Please pass a name on the query string or in the request body");
}

This simple function is triggered via Http and returns a string saying Hello {name}. The name is passed in via a query string parameter.

Azure Functions are made up of 2 primary things, Triggers and Bindings.

Triggers

A trigger is how a Function is started, as we see the sample above is a HttpTrigger. Functions has a number of triggers that we can use including:
Http trigger – function that runs when a http end point is hit
Timer trigger – runs on a schedule
Queue trigger – runs when a message is added to a queue storage
Blob trigger – runs whenever a blob is added to a specificied container
EventHub trigger – runs whenever event hub recieves a new message
IoT Hub Trigger – runs whenever an iot hub recieves a new event on the event hub endpoint
ServiceBus Queue trigger – runs when a message is added to a specified Service Bus queu
ServiceBus Topic trigger – runs when a message is added to s specified ServiceBus topic
CosmosDB trigger – runs when documents change in a document collection

In this post we’ll be focusing on the Cosmos DB trigger.

Bindings

Bindings are a way of declaratively connecting another resource to the function eg binding to Cosmos DB provides you a CosmosDB Connection and allows you to insert data into CosmosDB; bindings may be connected as input bindings, output bindings, or both. Data from bindings is provided to the function as parameters.

Cosmos DB

Cosmos DB is a multi-model global scale database with easy single touch geo-replication. It’s fast and has more SLA’s than any another cloud database on the market. One of the advanced features we get in Cosmos DB is change feed, Cosmos DB Change Feed is a log of all the changes that have occurred on a collection in the database. You’ll be able to see inserts, updates and deletes. As mentioned before Cosmos DB has a Azure Function trigger that uses change feed, so in essence we can call a function every-time a CosmosDB is triggered and pass in the data.

You can see below we’ve got a Cosmos DB triggered functions, a list of changed documents is provided into the function.

[FunctionName("CosmosTrigger")]
public static async Task CosmosTrigger([CosmosDBTrigger(
    databaseName: "CDALocations",
    collectionName: "Location",
    ConnectionStringSetting = "AzureWebJobsCosmosDBConnectionString",
    LeaseCollectionName = "leases",
    FeedPollDelay = 1000,
    CreateLeaseCollectionIfNotExists = true)]IReadOnlyList<Document> documents,
    ILogger log)
{
    if (documents != null && documents.Count > 0)
    {
        log.LogInformation($"Documents modified: {documents.Count}");
        log.LogInformation($"First document Id: {documents[0].Id}");
    }
}

To show you a diagram in a diagram see below:

CosmosDB Change Feed

SignalR Service w Real World Example

SignalR service is fully managed real-time communications service.

Let’s put this into a real world scenario. The legendary James Montemagno built a Cloud Enabled Xamarin Application called GEO Contacts which we can find on github here. Geo Contacts is a cross-platform mobile contact application sample for iOS and Android built with Xamarin.Forms and leverages several services inside of Azure including Azure AD B2C, Functions, and Cosmos DB. This application allows you to check into locations and see who else has checked into a location near you, which is awesome but there’s a little bit of missing functionality that I would like, if I’ve already checked into a location I would like to be notified if another person checks in near me.

The process flow will be as follows:
1) A user checks in at a location like Sydney, Australia
2) Azure Function is called to store the data inside Cosmos DB
4) The Azure Function that is subscribed to that change feed is called with the change documents
5) The function will then notify other users via SignalR.

Setup the SignalR Service

We first have to setup SignalR Service and integrate with the Functions and the Mobile app.

Go into Azure and create a new SignalR service.

newsignalr

Then take the keys from the SignalR service and add the configuration into the Azure Function.

signalrsettings

If we want to get the SignalR configuration into the client Azure Functions makes this easy. We create a simple function that returns the SignalR details.

[FunctionName("GetSignalRInfo")]
public static SignalRConnectionInfo GetSignalRInfo(
    [HttpTrigger(AuthorizationLevel.Anonymous)]HttpRequest req,
    [SignalRConnectionInfo(HubName = "locations")]SignalRConnectionInfo connectionInfo)
{
    return connectionInfo;
}

Then we create the signalr client in the Xamarin application.

var client = new HttpClient();
string result = await client.GetStringAsync("https://geocontacts2.azurewebsites.net/api/GetSignalRInfo");
SignalRConnectionInfo signalRConnectionInfo = JsonConvert.DeserializeObject<SignalRConnectionInfo>(result);

if (hubConnection == null)
{
    hubConnection = new HubConnectionBuilder()
                        .WithUrl(signalRConnectionInfo.Url, options =>
                        {
                            options.AccessTokenProvider = () => Task.FromResult(signalRConnectionInfo.AccessToken);
                        })
                        .Build();

    await hubConnection.StartAsync();
}

Setup the Change Feed

Now that we have the SignalR setup we can setup the change feed. The only thing we need to setup for change feed is the leases collection.

Now that we’ve setup the leases collection building the event driven function is very simple. We only need to use the CosmosDBTrigger with the correct configuration and then add the SignalR binding.

In the function below, we can see it uses the CosmosDBTrigger it gets executed when a location is added into the Location collection, we’re also have the SignalR binding which means we can easily sending messages to the Xamarin clients we setup.

[FunctionName("CosmosTrigger")]
public static async Task CosmosTrigger([CosmosDBTrigger(
    databaseName: "CDALocations",
    collectionName: "Location",
    ConnectionStringSetting = "AzureWebJobsCosmosDBConnectionString",
    LeaseCollectionName = "leases",
    FeedPollDelay = 1000,
    CreateLeaseCollectionIfNotExists = true)]IReadOnlyList<Document> documents,
    [SignalR(HubName = "locations")]IAsyncCollector<SignalRMessage> signalRMessages,
    ILogger log)
{

    List<LocationUpdate> locations = new List<LocationUpdate>();

    if (documents != null && documents.Count > 0)
    {
        log.LogInformation($"Documents modified: {documents.Count}");
        log.LogInformation($"First document Id: {documents[0].Id}");

        locations.Add(await documents[0].ReadAsAsync<LocationUpdate>());
    }

    await signalRMessages.AddAsync(
       new SignalRMessage
       {
           Target = "nearMe",
           Arguments = new[] { locations.ToArray() }
       });

    log.LogInformation($"Sent SignalR Message");
}

That’s it, now we have a scalable, real-time and event-driven system.

Leave a Reply