Messing around with Cloudflare workers

Posted on

It has been two years since I tried moving Pajthy to AWS. Back then I left the project unfinished - I wasn’t able to find a solution for the websocket migration that I was satisfied with. Now I’ll try again; but this time instead of AWS, I give a shot to Cloudflare’s Workers.

With the latest serverless migration effort, the work to migrate the backend functionality was well organized into the following topics:

  • wrapping the business logic serving into a function
  • implementing a new storage layer, with data consistency in mind
  • deal with the real-time messaging

In this post we’ll investigate what options we have for these issues.

Making a serverless function

Since “going with CF” is a given, there are not so many options for serverless functions. Luckily, after spending five minutes on finishing this introduction, Cloudflare workers are probably will work just as well as Lambdas did:

https://developers.cloudflare.com/workers/

One big difference between CF workers and AWS lambdas is that while Lambda used docker containers to wrap basically any kind of code into a function, Workers only accept JavaScript. Thus, the time has finally come to dig a little into WASM (since go code can be compiled into it).

Golang worker template

A quick google search gave a working example (even better: a template!) to have a similar quickstart function as the official intro had:

git clone git@github.com:syumai/worker-template-go.git my-go-cf-app
cd my-go-cf-app
rm -rf .git  # since it was just a template
git init  # otherwise build will fail on the VCS stamping

# get the dependencies
go mod init akarasz.me/my-go-cf-app
go mod tidy

# publish the function
wrangler publish

And the function is already available on the *.*.workers.dev domain.

Use a custom domain

Adding a custom route is working as well, though (for me) it was not tirival how to do it. While I have my domain at CF, defining a subdomain on the Worker’s settings page was not enough - I also had to create a DNS entry as well; the only requirement for this entry has to be a Proxy enabled entry.

So the solution was to

  1. Create example.akarasz.me as CNAME record pointing to @ (could’ve point anywhere else)
  2. Add example.akarasz.me/* as a custom domain to the worker

Storage layer: durable objects

I jumped through the Worker KV that CF also supplies for storing data; there are no options for reaching data consistency. At least this is what the brochure said that lead me to durable objects right away.

I have to confess, I got the whole durable objects concept wrong at first. My original understanding relied on the false assumption that durable object is just a marketing name for a better KV storage 🤦 but as it turns out, it is much more.

The bottom line is, DO is basically a “singleton” worker. If you send the request to the same DO (that was looked up by the same ID), then you’ll get the same instance, thus you can apply all your concurrency handling you are familiar with. Also, the durable object is not just a data object, it is more like a worker with state; the actual data storing is happening through a different subsystem. You could even use the standard KV store option instead of that if you wish.

A durable object has a fetch() method implemented, this is the entry point. It even gets called like an http endpoint - this is how we can transform the request before we store the value.

Other workers are communicating with DO objects through generated stubs. The stubs are accessible through the namespaces that are getting generated because of the wrangler.toml configuration.

let id = OBJECT_NAMESPACE.idFromName(name)
let stub = OBJECT_NAMESPACE.get(id)
let response = await stub.fetch(request)

The original doc of the durable objects is a very good place to learn more about the concept and all.

Since golang is not a supported platform for Workers, it might be tricky to get an access to the DOs from a WASM… this will be an interesting challenge.

Real-time messaging

Three options, all of them have their pros and cons. Unlike how people do it usually, this time the second one seems like the way to go. Nothing is decided yet though.

Durable objects for websockets

Thanks to the nature of the durable objects, they could be used for managing websocket connections and use them to broadcast the state changes for the clients.

Pro: I would remain fully within the CF domain

Con: every second is getting billed when there is even a single websocket connection open to the worker. Since I already know that people are not big on closing the webpages they are not using anymore, this could get costly 😅

Use a third party provider

Since serverless and websockets are not a match made in heaven (after all, this is where the Serverless AWS effort took a hit), it might be worth to try out a 3rd party, real-time messaging provider. Without spending too much time on it, found (ably)[https://ably.com/docs/quick-start-guide] that looks promising, addressing this concrete issue we have here.

Pro: could mean a working solution, pricing is based on sent messages and not duration of holding a connection.

Con: would not remain within the CF domain.

Use CF Pub/Sub

Without saying too much: https://developers.cloudflare.com/pub-sub/

Pro: Looks like the perfect solution, pubsub is exactly what happens in our case, websocket is supported and (hopefully) pricing would be based on the number of messages too.

Con: it is in currently in a private beta. Bummer.

Conclusion

If we believe that the same basic steps are the ones we need to take now that we had to take with the AWS serverless migration, then I am confident that the Cloudflare platform (with a little help from Ably) will be more than enough to finish the migration this time 🤞.

It would also be cheap - while at AWS I even had to pay $0.50 to even have my domain hosted, here the whole service (with the current average load) should easily fit into the basic paid plan, without any overpayment; this means a $5 monthly fee. We’ll see in the end how much I was off 😅

In the next part, where I’ll either dig deeper the capabilities of Ably, or try to figure out how to access durable objects from a worker written in go. Stay tuned!