This post explores serverless development with practical examples of using Google Cloud components through the development of a Twitter bookmarking bot. The bot lets users save and retrieve links by sending direct messages to the bot’s Twitter user or by mentioning the bot in tweets.

The bot uses Google Cloud Functions for its business logic, the NoSQL database Google Cloud Datastore for storing and querying links, and the message queue Google Cloud Pubsub for communication between its functions.

What is Serverless?

Serverless, or functions-as-a-service, lets developers create applications by implementing functions of event-driven business logic. Serverless platforms deploy the functions inside short-lived containers in response to events that trigger the functions. Such trigger events include HTTP requests on external endpoints or activity in cloud components, such as messages posted to queues by other serverless functions.

Serverless offers near-infinite scalability for the functions as the platform deploys enough function containers to handle the amount of trigger events occurring at any time and lets application developers ignore operational aspects of running the application.

Development in production

Serverless platforms support writing functions in multiple programming languages by providing containers with different language runtimes and support for calling and monitoring each language’s functions when trigger events occur.

I am personally most at home with statically typed languages such as Java, Scala, and Rust, but felt this bot was a small enough project to perhaps be done more productively with the inline JavaScript editor provided by Google Cloud Functions.

The inline JavaScript editor keeps the code entirely in Google Cloud and whenever one saves a change to the code the new version of the function will be deployed inside a NodeJS container to handle the next trigger event. The code is debugged and monitored through the Google Cloudstack logging for the functions.

Functional specification of the bot

Twitter will interact with our bot by posting HTTP requests to an endpoint, a webhook, that we register with the Twitter API. The endpoint must be able to respond to hourly cryptographic activity challenges and Twitter will post periodic batches of direct messages and Tweet-mentions to the endpoint as JSON data. Our bot will need to parse all the direct messages and and Tweet mentions and separate them into commands for the bot. The bot should support the following commands

The bot should save links that a user sends to the bot user in a direct message or Tweet-mention. The links should be annotated with any hashtags included in the message. If the user sends a direct message containing the word “list!”, the bot should respond with a listing of a few of the user’s links. If the message contains hashtags, only links with those hashtags should be selected.

To open up the bot for public use one would want to allow users to register with the bot so it follows them and lets them send direct messages to the bot user, but this is left for later versions and I just make the bot follow my own user through the Twitter UI for now.

Design

Our bot will be implemented as two serverless functions

A twitterHook function exposes an endpoint and is triggered by HTTP requests from Twitter. This function handles responding to Twitter’s cryptographic activity challenges and receives and parses posts of batched activity data. It separates the batches into individual commands to either store a link or perform a listing and publishes the commands as messages to a Google Pubsub command topic. A performCommand function is triggered by a message on the command topic and performs a single command, either storing a link in Google Cloud Datastore or performing a query for links and sending the result as a Twitter direct message.

We could perform all this work in a single function, but that would require chaining asynchronous datastore and list-sending operations and would complicate the code and error handling. It could possibly also make the serverless function take too long to complete for large batches if there is slowness or timeouts when sending direct messages to the Twitter API. To be able to quickly scale up and down, serverless platforms only let functions run for a few minutes before killing them so functions should complete quickly.

We could also split the work of the performCommand function into two separate functions, one that stores links and one that responds to list commands, but this would either require posting to two different topics, or lead to unnecessary function invocations if both functions are triggered by the same topic for every message.

A third alternative would be to create an additional topic for result listings and have the performCommand function post to this topic instead of sending direct messages through the Twitter API. A separate function would then be triggered by the result listing topic and send out the direct messages. This could lead to longer latency for list commands if those happen infrequently.

A function that is called only infrequently can take longer to complete as it is less likely to have a running container available that can be re-used to process a trigger event. Starting a new container adds extra time to the first function execution, this is known as serverless’s cold start problem.

Google Cloud Function as a Twitter webhook

Once we have created a user and application for our bot in the Twitter developer console, the Twitter API lets us register an endpoint, a webhook, that will be called with notifications about activity on the account. These notifications include when the account receives direct messages or when tweets are created that mention the user. For the webhook to be approved by Twitter, it also needs to respond to an initial cryptographic registration challenge and periodic challenges that Twitter will send hourly.

We create the Google Cloud Function below and configure it to be triggered by an endpoint that gets generated by the Google Cloud console. As the functions returns after having triggered asynchronous publishing operations that will complete through callbacks at a later time, it is important that the function and callbacks call send on the response object to let the platform know when the work has completed.

Environment variables such as APP_SECRET come from the Twitter developer console for our bot application and have been specified in the configuration of the twitterHook function in the Google Cloud console (I know, Google recommends against storing secrets in environment variables, but an enterprise vault is not an option for this project).

After creating the function we also need to send requests to the Twitter API to register our function’s endpoint as a webhook and subscribe to the activity of the bot user.

Google Cloud Pubsub

Google Pubsub is a message queue that lets different components in the cloud communicate by publishing and subscribing to topics. As our twitterHook function publishes messages to the topic commands on Google Pubsub, we need to create the topic in our project and give our function permission to publish to it. We can create the topic when selecting a trigger for our next function.

We create the performCommand function and select that it should be triggered by messages on a Pubsub topic, this gives us the opportunity to also create the commands topic. While our new function will now be ready to receive calls when new messages are published to the topic, we still need to register our twitterHook function’s user as a publisher for the topic in the Google Cloud Console’s configuration for Pubsub.

Command function and Cloud Datastore

The performCommand function below gets called when a command message is published to our Pubsub topic. It parses the command and either stores a link to Google Cloud Datastore, or queries for links and sends the result to the Twitter API as a direct message to the requesting user ID. The function notifies the platform that it has completed by invoking the provided callback.

The data model has a hierarchical key with the user ID at the top and links stored as sub-keys and tags stored in an array property. Querying is done by specifying the user key and adding result filters for tags.

Issues

A sign of Google Cloud Functions’s immaturity, that became additionally annoying by working in a dynamically typed language, was that the editor auto-generated the wrong signature for the performCommand function. The function gets called with 3 arguments (the event, a context, and the callback) but the editor left out the context and generated only 2 arguments, so invoking the callback on the generated signature resulted in “not a function” error as the second argument was really the context.

Final impressions

It was really quick and friction-less to get started with Google Cloud Functions and its JavaScript editor as one can skip configuring servers or packaging the application, and projects with little traffic fit comfortably inside Google’s free usage tier for the service.

However, the limitations of this form of development were felt as the complexity of the system grew, even for a small project like this. I suspect a typed SDK with compiler support would have ended up just as productive, though each deployment step would have taken longer than just pressing save in the editor.

Serverless’s focus on distributed scalability and heavy reliance on proprietary cloud components means there are few best practices for developing, testing and maintaining full multi-function applications. This is probably something that will need to emerge for wider industry adoption. It is possible serverless functions can lose ground to alternatives such as serverless container platforms, like AWS Fargate and Kubernetes Knative, that offer a different deployment unit with more established practices.

The scalability of serverless is both appealing and scary as there are few limits to both performance and cost. Programming errors that create loops of functions, or load attacks that lead to lots of activity in the platform’s components can get very expensive and I have yet to discover easy ways to limit costs in Google’s platform (though a project like this Twitter bot has some protection from malicious activity as the Twitter API does rate-limiting and batching of user activity).

Pros and Cons of Serverless Functions

Pros

Enormous scalability

Enables super quick development and deployment

Low upfront costs and whenever there is little traffic

Cons