API – An Application Programming Interface is a documented and consistent way of communicating with a web app or service that abstracts the functionality away from how that functionality actually works. In other words, an API call to update, let’s say, your email address in an accounts service will remain the same, even if the backend code for that service is completely rewritten (or if a new version of the API is released.) RESTful APIs use http requests and verbs (such as GET and POST) to make distinct and isolated (stateless) requests for resources, which are then returned in a standardised format, such as JSON. That’s what I’ll be using here.
Serverless – This is a broad term which describes a cloud-native platform or environment that allows developers to build and run apps and services without having to provision or manage a server themselves (there is obviously a server down there somewhere, it’s just abstracted out of the user experience.) Two such platforms on GCP are Cloud Run and Cloud Functions. Cloud Functions allows for snippets of code to be executed on demand. Cloud Run does something similar but instead of code snippets, hosts fully self-contained apps, in the form of containers (see below), which can scale instances up and down, based on demand. I’ll be using the latter, because its awesome.
Containers – This is when code is packaged with all the dependencies that it needs in order to run. This means that, rather than having to either dedicate a whole VM to one app (and having to provision additional VMs in order to meet additional demand for that app) or having multiple apps competing for resources on a single VM, apps can be entirely self contained, with instances scaling up and down at need. It also allows for modern microservice system designs whereby, rather than having a single monolithic tangle of code representing all your needs, functionalities can be broken down into distinct and independent services which use APIs to communicate with each other. This has various benefits, including that you can use the right language for the right task, and that distinct services can be revised and debugged without disturbing your wider codebase.
GCP offers a great quickstart guide to deploying a Python-based web service to Cloud Run, which I’ve used as the basis for my API. It uses the framework Flask to run a little web server which executes functions based on the URL path specified by the client. I can use this to specify a path (or “route”) of “/visits” that allows http API requests made using the POST method, which will return the output of a function called “counter()”:
Next, I need to connect the app to my database. In the first instance, I don’t need to do very much to achieve this. First of all, I need to locally install the Python Datastore client libraries with:
pip install --upgrade google-cloud-datastore
…and then connect to my database and instantiate the client object, which will provide the various methods required to work with it, like so:
In terms of authentication, by default Cloud Run will use the default runtime service account with other apps and services in the same project. I will later follow best practice and give the app a dedicated service account with the minimum possible permissions required to work with the database but, for initial testing, I can leave it as is.
The intended functionality here is that, when a request is made via the “/visits” route, the app will query the database and return the current number of visits. It will at the same time, update the value of the “count” property by 1. To do this, I’m going to create two functions get_visits() and set_visits(), both of which will be called in order by the counter() function, which will then return the updated visits value in the body of the response to the API call.
In the get_visits function I’ll use the query method of the client object to return the database entity of the kind “analytics”, and then assign that to a variable in the form of a Python list. As I know the list will only have one item, I can avoid iterating over it and just refer to the ‘count’ value by name. I can then return that value + 1, so that I can display the new count and have a new value ready to update the database with in the next step:
Then in the set_visits function, I can supply the updated visits count as an argument, and use that to update the count property with the “put” method:
Putting this all together and calling both functions in my “/visits” route, gives me the following:
Running this locally with:
$ python app.py
…shows me that it works (yay!) I’ve already thought of a couple of ways this could be more efficient but it’ll be good to have changes to make during the testing and automation stages so I’ll live with this for now 🙂
One final thing before deployment. Cloud Run uses Docker to build the containers it runs. Helpfully, the quickstart guide already configured a Dockerfile for me but – as it didn’t know I was going to be using Datastore – I need to manually add that library to the ‘requirements.txt’ file:
It’s then super easy to deploy the app directly to Cloud Run with:
$ gcloud run deploy
Once that’s finished, it generates a URL I can use to call my Cloud Run service. I can’t just check this in the browser as I’ve specified the need for a POST request, however I can use the brilliant Postman app aaaaaaand…