Follow me on Twitter, happy to take your suggestions on topics or improvements /Chris
This article is part of #ServerlessSeptember. You'll find other helpful articles, detailed tutorials, and videos in this all-things-Serverless content collection. New articles are published every day — that's right, every day — from community members and cloud advocates in the month of September.
Serverless is great in many ways.
Your first impression of Serverless might be that it's saving you money. You've realized that you have some code that can run seldom and you've moved that and made Azure functions out of it. Great, you're already happy at this point, who doesn't like more resources? Then you discover something else that's equally great or even greater. Because it lives in the Cloud it can take to all your services in the Cloud with almost no configuration. How come it's this easy? It's made easy cause we are using something called bindings. So let's talk about Serverless and why and how bindings are part of the success story that is Serverless
In this article we will cover:
- Serverless, what is it, why would we want to use it?
- Core concepts, let's cover bindings and trigger
- Demo, let's build something using Bindings and Databases and show how easy it is
Resources
Sign up for a free Azure account
If you want to build Logic Apps and Azure Functions you will need an Azure account, it's free.Great example page for using Triggers and bindings in different languages
Great article on CosmosDB and Azure Functions
Great article, great blog in general
Serverless
Serverless is on everyone's lips right now. Serverless this Serverless that. Why is that?
Part of the reason is that Cloud nowadays has become the default hosting platform, no more server rooms almost. The Cloud is really someone else server room but the bottom line is that you don't have to care. There are so many things the Cloud helps with, security, reliability, backup, scalability and most of all the ability for other things that live in the Cloud to talk to each other.
So why is that great?
Most companies have a ton of services that together makes out the IT landscape they call a business. Getting all or most of these services to talk to each other is an all-consuming task or at least it requires a lot of resources. Most Clouds today are really good at connecting these services within the Cloud and even cross Cloud.
So where does Serverless fit in?
Serverless has arrived after many steps of cloud evolution in which we first decided to NOT manage the hardware anymore. Then we decided that we didn't want to deal with app servers. So we arrived at the point where we just wanted to focus on code, code, and nothing but the code. That meant a whole new platform was born or FaaS, function as a service. With the arrival of Serverless, we realized something else. We started to realize more and more we only wanted to pay for actual code execution and that's just what Serverless offers - fully managed and pay for what you use.
That's not why we read this article though?
Correct, we are here to learn about how to integrate Serverless functions with other things using something called bindings.
Core Concepts
There are three major concepts we need to understand when it comes to integration and Serverless, those are
- Triggers, this is how our function can be invoked. What causes an invocation could be everything from an HTTP call, new database entry to a new message on a queue and even more other things. The Point is, triggers are what starts our function.
- Input binding, this a connection to some kind of data source. The point is to read data from this data source. This could be everything from an Excel document, Database or a Queue
- Output binding, this is us creating a connection to a database with the explicit intent of wanting to change data. Typically we get access to a record that we can populate and this record goes into the data source to typically update or create new data
There, now we know the most important concepts that we need to know to get started.
Demo
Ok so what are we building? Well, to showcase how great Serverless let's try to connect with at least two things. That means we will need to build/provision:
- Database, we will provision a CosmosDB Database
- Azure Function App, of course, we will need to build an Azure Function App that connects with our database
Database
This step is pretty straight forward. What we need to do is log in to the Azure Portal. You do have an account right? If not, create a free one here Free Azure account
Here are all the steps we need to take:
- Provision our Database
- Set up the database and add sample data
Provision our Database
Ok, so we have logged in to the portal. Now let's provision a database like so:
Thereafter we need to enter some information about our database. Below we have indicated all the mandatory fields we need to fill in. Ensure you select API
as Core(SQL)
and also ensures you have picked a Location
that's close to you for best response times.
Lastly, hit Review + Create
at the bottom. This should trigger provisioning. A few minutes later your resource/database should be ready for use.
Set up the database
Now our resource has been provisioned and it's ready for use to configure it. The first thing we are going to do is to add a Container.
What is a container?
Glad you asked :) A container is what actually will hold our data or entities. The container itself contains something called documents.
To create the container we select Data explorer
from the left menu and then click New Container
, like so:
Next step is to fill in all the details needed to create a container.
It will ask you to fill in the following fields:
- Database id, you can give it whatever name you want, I choose to name it database-chris
- Throughput, give it the value 1000
- Container id, this is the name of the collection. A collection holds a list of documents. Every document is like a row in a table, with table and collection being roughly the same thing. Give it the name Bookmarks
- Partition key, The partition key specifies how the documents in Azure Cosmos DB collections are distributed across logical data partitions. What does that even mean? It means this database scales in the Cloud. We tell it how to scale and how to use a technique used sharding to do so. Give the value /id
The form will look like this:
Add sample data
Ok now the database is set up and we can start filling it with some data. So the next step is to click our Collection
bookmarks and then Items
. Thereafter click Net Item
to insert a record in our collection, like so:
As you can see to the right it has opened up a textarea that we can use to insert data. Let's change that to the following:
{
"id": "docs",
"url": "https://docs.microsoft.com/azure"
}
and click Save
. Add a few more records like so:
{
"id": "portal",
"url": "https://portal.azure.com"
}
and a last one:
{
"id": "learn",
"url": "https://docs.microsoft.com/learn"
}
Azure Function app
Ok, then we have a database, a data source. Now it's time for us to build an Azure Function app and an Azure Function and really demonstrate how easy it is to connect our data source. We will do the following:
- Scaffold an Azure Function app and an Azure Function
- Set up a connection to the database
- Read from our database and thereby learn about input bindings
- Write to our database and thereby learn output bindings
Prerequisites
To be able to scaffold our Azure Function app we need a few things installed namely
- Azure Core Tools
- Azure Functions extension for VS Code, Extension
- A free Azure Account or an existing one if you have that, Free account
Ok, all setup?
Good, let's continue! :)
Scaffold an Azure Function App
Open up VS Code. Select View/Command Palette
(Ctrl + Shift + P on Windows, CMD + Shift + P on Mac).
Start typing for Azure Function: Create new Project
and select it.
- Select the current folder
- Select C# as language
- Select HttpTrigger for our first function
- Give it the name
Bookmarks
- You name the namespace anything you want but let's go with
Company
. - Choose the authorization type
Function
A window will popup to restore dependencies
. Click that or if you miss it head to the terminal and type dotnet restore
. This is so it downloads all the dependent library that it has specified.
Ok then. We have gotten ourselves a function called Bookmarks
, it resides in the Bookmarks.cs
, like so:
Let's open up Bookmarks.cs
shall we?
// Bookmarks.cs
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System.Collections.Generic;
namespace Company
{
public class Bookmark
{
public string Id { get; set; }
public string Url { get; set; }
}
public static class Bookmarks
{
[FunctionName("Bookmarks")]
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");
}
}
}
What we are most interested in is the top of our function, let's zoom in on that:
[FunctionName("Bookmarks")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log
) { /* function body */ }
We can see that the attribute FunctionName
is used to determine that we are dealing with a function and that it's called Bookmarks
. Then we can see that we have an attribute HttpTrigger
that says it can be triggered by an HTTP call.
Adding CosmosDB input binding
Ok, so how do we add CosmosDB to this? Well, there are two ways:
Add the CosmosDB attribute to our
Bookmarks.cs
file and then install the correct NuGet package from the terminal to ensure everything compilesAdd a function with a CosmosDB trigger, this will install the needed dependencies and will also trigger a storage dialog that makes you choose a storage account, the name of your database and your collection
Let's try out the first option. First, let's open up a terminal and type:
dotnet add package Microsoft.Azure.WebJobs.Extensions.CosmosDB
Now we are ready for the next step which is to add an input binding to our Bookmarks.cs
file.
To do that we will use an attribute class called CosmosDB
. We need to give it some values namely
- database name, this is what we named our database so that's database-chris
- collection name, we named our collection Bookmarks
- connection string, as for connection string, this is something we can find in our Azure Portal. Go to your resource click Keys in the menu and then copy the value displayed in PRIMARY CONNECTION STRING
Now, database name and collection name are things we can add in the code, like so:
// excerpt from Bookmarks.cs
[FunctionName("Bookmarks")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
[CosmosDB("database-chris", "Bookmarks", ConnectionStringSetting ="CosmosDB")]IEnumerable<Bookmark> bookmarks
ILogger log
) { /* function body */ }
So above we have added the attribute class CosmosDB
and decorated the parameter bookmarks
. We can see that bookmarks
is of type IEnumerable<Bookmarks>
where does the type
Bookmark
come from?
That's a type we need to create, either in Bookmarks.cs
or it's own file, but it needs to have the following shape:
public class Bookmark
{
public string Id { get; set; }
public string Url { get; set; }
}
It has the properties Id
and Url
to match the shape it has in the database.
Looking further at the code, we give ConnectionStringSetting
the value CosmosDB
where does it come from?
It comes from a file local.settings.json
and its Values
property, like so:
{
"IsEncrypted": false,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "dotnet",
"CosmosDB": "value of primary connection string"
}
}
Trying it out
Let's first set a debugger on the first row of our function in Bookmarks.cs
.
In the menu of VS Code select Debug/ Start Debugging
.
Go to the browser and input http://localhost:7071/Bookmarks
and your breakpoint should be hit, like so:
As you can see our bookmarks
variable is populated with data, we are talking to the database and it is giving us data, success :)
Limiting our response
Now the above is great, we get data back from the Database. However, we might have millions of records in there, how do we limit our response?
That's easy to do, we need to add two parameters to our CosmosDB
attribute, namely:
- Id
- PartitionKey
It's worth learning about partition keys. So have a read here Partition keys for CosmosDB
Ok, so what we will do is to add a new function that will be able to take a query
parameter from our trigger and use that to query CosmosDB. So add the following function:
[FunctionName("GetBookmark")]
public static IActionResult GetBookmark(
[HttpTrigger(AuthorizationLevel.Function, "get", Route=null)] HttpRequest req,
[CosmosDB(
"database-chris",
"Bookmarks",
ConnectionStringSetting = "CosmosDB",
Id = "id",
PartitionKey = "{Query.id}"
)]Bookmark bookmark,
ILogger log
)
{
return (ActionResult) new OkObjectResult(bookmark);
}
Now start up our application and run the following URL in the browser http://localhost:7071/api/GetBookmark?id=portal
. This will ensure that our CosmosDB is queried for id
with the value portal
. The result should be this:
Output bindings
We used input bindings to read from a database. Output bindings are used to write to a database. Let's look at two different scenarios.
- Create, we will take query parameters from our trigger and use those to create an entry in our database
- Update, We will take an existing entry and update that
Create
Ok then, what's needed to create an output binding? Well, it's almost nothing. The only thing we need is a new parameter in a function of type out
, like so:
out dynamic bookmark
Let's create a new dedicated function though and call it CreateBookmark
and define it like so:
[FunctionName("CreateBookmark")]
public static IActionResult CreateBookmark(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route=null)] HttpRequest req,
[CosmosDB("database-chris","Bookmarks",
ConnectionStringSetting = "CosmosDB")]out dynamic bookmark,
ILogger log
) {
string id = req.Query["id"];
string url = req.Query["url"];
bookmark = new { id = id, url = url };
return (ActionResult)new OkObjectResult("created");
}
Now fire up the program and enter a URL like so http://localhost:7071/api/CreateBookmark?id=devto&url=dev.to%2Fsoftchris
in the browser. That's it, this should create new entry. Don't believe me? Have a look at your database in the portal:
Update
Well, because this is CosmosDB. This means that if there is a pre-existing record with that id value if will simply be replaced. However, that might not be what you had in mind. If you only wanted to update certain properties I recommend the following approach:
- Use an input binding to retrieve an existing record
- Copy the values from that input record to the output binding
- Copy whatever values you get from a trigger to the output binding
That's it, that's the two update scenarios we have.
Summary
We've learned about input bindings as well as output bindings. We learned that input bindings allowed us to read from the database and output bindings allowed us to create or update data. Additionally, we've learned how to provision a CosmosDB database and connect to it using an attribute class called CosmosDB. It's definitely possible to have CosmosDB as a trigger as well but that will be another article :)
Top comments (9)
Hi Chris
Thanks for the awesome article. Just wondering if it is possible to have the single inbound trigger with two outbound bindings. The first being a CosmosDb collection and the second being a queue.
The queue message however needs to have the Id of the entry in the CosmosDb.
OR
Would it be better to have it write to the collection in using the inbound trigger and then create a second function with the CosmosDbTrigger which would be triggered when the new document is added. This would then write to a queue from processing.
You can have two output bindings.. I would say though that your second scenario is what I would do.
Hi Chris,
Thanks for the response...I appreciate it. On a seperate topic, have spend quite a fair amount of time on R&D with regards to Azure Index Service, but I am quite interested in how you would utilise Azure Storage. In particular, if I have a bunch of image thumbnails which I have created using a resizer function in Azure...how do I serve these to my web application?
I currently have an onprem application that serves them from a server disk...would I just use the thumbnail URI generated from Azure when it is stored? And if so, how could I use a custom domain for them i.e. myapplication.com/thumbnail/{id}
Hi Chris,
Really good article!
The first c# class example, Id should be of type string, not int.(As I was getting an Exception binding Parameter 'bookmark'. Newtonsoft.Json: Could not convert string to integer).
I'm attempting this demo in vs2019.
Cheers,
Ferdeen
hi Ferdeen.. Yes I've noticed myself. The code is changed later in the article, I missed that first instance. I'll update.. Sorry you got stuck.
Could you provide an example using vs2019 as well?
sure, in a future article :)
Really good post! I started using Azure Functions recently and this helped me understand them better :)
Thank you Mateusz :)