Azurite and Table Storage in a dev container
It's great to be able to develop locally without needing a "real" database to connect to. Azurite is an Azure Storage emulator which exists to support just that. This post demonstrates how to run Azurite v3 in a dev container, such that you can access the Table Storage API, which is currently in preview.
Azurite in VS Code
Azurite v3.12.0 recently shipped, and with it came:
Preview of Table Service in npm package and docker image. (Visual Studio Code extension doesn't support Table Service in this release)
You'll note that whilst there's a VS Code extension for Azurite, it doesn't have support for the Table Service yet. However, we do have it available in the form of a Docker image. So whilst we may not be able to directly use the Table APIs of Azurite in VS Code, what we could do instead is use a dev container.
We'll start by making ourselves a new directory and open VS Code in that location:
We're going to initialise a dev container there for function apps based upon the example Azure Functions & C- .NET Core 3.1 container. We'll use it later to test our Azurite connectivity. To do that let's create ourselves a .devcontainer directory:
And inside there we'll add a devcontainer.json:
As we can see, we're referencing a docker-compose.yml file; let's add that:
It consists of two services; app and azurite. azurite is the Docker image of Azurite, which exposes the Azurite ports so app can access it. Note the name of azurite; that will turn out to be significant later. We're actually only going to use the Table Storage port of 10002, but this would allow us to use Blobs and Queues also. The azurite service is effectively going to be executing this command for us when it runs:
Now let's look at app. This is our Azure Functions container. It references a Dockerfile which we need to add:
We now have ourselves a dev container! VS Code should prompt us to reopen inside the container:
Make a function app
Now we're inside our container, we're going to make ourselves a function app that will use Azurite. Let's fire up the terminal in VS Code and create a function app containing a simple HTTP function:
We need to add a package for the APIs which interact with Table Storage:
The name is somewhat misleading, as it's both for Cosmos and for Table Storage. Famously, naming things is hard 😉.
Our mission is to be able to write and read from Azurite Table Storage. We need something to read and write that we care about. I like to visit Kew Gardens and so let's imagine ourselves a system which tracks visitors to Kew.
We're going to add a class called KewGardensVisit:
Now we have our entity, let's add a class called HelloAzuriteTableStorage which will contain functions which interact with the storage:
There's a couple of things to draw attention to here:
AZURITE_TABLESTORAGE_CONNECTIONSTRING - this mega string is based upon the Azurite connection string docs. The account name and key are the Azurite default storage accounts. You'll note we target TableEndpoint=http://azurite:10002/devstoreaccount1. The azurite here is replacing the standard 127.0.0.1 where Azurite typically listens. This azurite name comes from the name of our service in the docker-compose.yml file.
We're creating two functions SaveVisit and GetTodaysVisits. SaveVisit creates an entry in our storage to represent someone's visit. It's a hardcoded value representing me, and we're exposing a write operation at a GET endpoint which is not very RESTful. But this is a demo and Roy Fielding would forgive us. GetTodaysVisits allows us to read back the visits that have happened today.
Let's see if it works by entering func start and browsing to http://localhost:7071/api/savevisit
Looking good. Now let's see if we can query them at http://localhost:7071/api/gettodaysvisits:
Disco.
Can we swap out Azurite for The Real Thing™️?
You may be thinking "This is great! But in the end I need to write to Azure Table Storage itself; not Azurite."
That's a fair point. Fortunately, it's only the connection string that determines where you read and write to. It would be fairly easy to dependency inject the appropriate connection string, or indeed a service that is connected to the storage you wish to target. If you want to make that happen, you can.
Discussion in the ATmosphere