Getting Started with Durable Objects
Come watch an introduction, with examples, to using Durable Objects!
Read the blog post: https://blog.cloudflare.com/durable-objects-open-beta/
All right. Hey, everybody. My name is Greg, and I'm a product manager on the Durable Objects team here at Cloudflare.
And what I'm going to be doing today is taking you through just a few examples of how to deploy Durable Objects.
So if you're not familiar with them, Durable Objects are Cloudflare's approach to state at the edge.
And you can read a bit more on those. It might be helpful to read a little bit through our docs to follow along today.
But what I'm going to be showing you is how to deploy a very simple application, and then also point you towards some other examples and things that you can go take a look at to learn a little bit more.
So what we'll be working off today is this Durable Objects example that we've deployed.
This is a template that you can use to deploy your own Durable Object out.
It also contains a really basic counter that we'll see how that works in a minute.
And then from there, we'll look at the chat demo example that we've also pushed out.
We have a few more examples we're working on internally, and those might come out over the next few weeks.
But these are the ones we have for now.
So one page I'll say is really super helpful is the Cloudflare Workers documentation.
So if we go over to Durable Objects docs, just search that. This using Durable Objects page will kind of walk you through a lot of what we're doing today, and that's a great resource.
So I'll be switching back and forth between the web view and then my terminal and then some code and things today.
So it might take me a second to go between those.
So just looking at this template we have here, this is a Wrangler template.
So Wrangler is our command line tool that we use to configure and deploy workers to Cloudflare's network.
What's great about Wrangler, and I'll switch over sharing now.
What's great about Wrangler is it understands what a template is.
So I look here and I type in Wrangler generate and I pass it in the template that I have here.
I'll give it a new name because I was doing this before.
This is just the link to the template that we have up here.
Wrangler will actually go ahead and go and clone that Git repo and actually go create a new Wrangler project for me.
The main thing I have to do is update my Wrangler .toml, which lets Wrangler know where to actually deploy out my demo tool.
I'll use four so you know I'm not cheating.
And yeah, so now we're ready to actually deploy out this example.
So before I kind of tell you what the example actually does, I will go out and deploy it just so you can see how we go ahead and do that.
Actually probably better if I show you. So let's go look at the code.
Come over here.
All right, so this is the actual code for the durable object that I've deployed that I'm going to deploy.
And so it sort of helps here to talk about what a durable object is and what a worker is.
So there's sort of two pieces to any application that uses durable objects.
There's the Cloudflare worker that handles the user's request that's coming into the network, and then there's the durable object itself, which is responding to a request made from that worker with a given ID.
So for example here, if you can see this, the top portion of this script right here is the worker that I've written.
And we're writing this worker, if you've used workers before, we're writing this worker in a slightly different format.
This is a module-based worker, which you have to use when using durable objects.
And so what we said here is in this default namespace, we've registered a fetch handler.
So when a request comes into Cloudflare's network, it's going to call this fetch handler and then do whatever the fetch handler says to do.
And in this case, we're going to call this async function handle request, await the response, and then return the response back to the user.
So here, this is where the durable objects magic begins. We're going to handle this request.
And we handle the request, we're going to go get the durable object that's in the namespace counter with the ID A.
And this sort of is a, it's a fetch stub.
It basically tells the runtime, the worker's runtime, get me a way to communicate with the durable object in the counter namespace that has the ID A.
And then you can actually go and retrieve that object itself, and then send a request to it.
So here's where we're actually sending the request. We're passing to the request is just from our Cloudflare request object, the URL.
So the object will actually just receive the URL that we sent to it.
We're going to wait for the response.
And then we're going to take whatever was responded back to us, and we're going to turn it back as durable object A count plus count, if that makes sense.
And so this, if you've noticed by now, is a durable object that is a simple counter, essentially.
The durable object itself is defined in its own class. So you can see this is a different export over here.
We've exported a class named counter.
And we have a few pieces here. We have the constructor. So the constructor is the code that the Cloudflare runtime will call whenever a new instance of your class is created.
So whenever, when we first go to get the object with ID A, we're going to call this constructor.
And all we're going to do there is take the state object that we pass in and store it on the object itself.
And so then you'll notice down here we have the same sort of async fetch handler that we had defined above in our worker.
And this is going to process the actual HTTP request that's coming in from the worker itself.
So here, basically, when we call this, when we call that request is going to be handled here within the durable object.
And the first thing we're going to do is we're going to check if we're initialized or not.
Checking for initialization is really important. This is how we check to see what the status of our object is.
So with durable objects, you don't necessarily know their lifecycle.
So you notice here there's no sort of create step when I go and actually access this object.
And that's because if a durable object doesn't actually store any state durably, we might just go clean it up and your application isn't actually aware of that.
It doesn't actually have to manage any sort of lifecycle, which is a great advantage.
But it also means that when you go ahead and actually first access your object, you're not sure if you've actually initialized that in-memory state yet.
So what we do here is we call this initialize.
If we don't have an initialized promise, we go call initialize.
And what initialize does is it goes and actually uses this special state .storage method.
So if you notice here, we're using the state object that was passed into us.
State has a property called storage. And then that has a method on it.
Sorry, object called storage that has a method on it, which is .get. So storage is actually how you access persistent storage from an object.
So some people believe that durable objects automatically persist their state.
That's not true.
It still provides you access to a transactional storage API, but there's no automatic persisting of in-memory values.
So what this says is go check if there's a value called value in storage, and then store on the object, either initialize it to zero or give me the value that was stored in storage.
We await that promise to let all of that finish.
And then you remember above, we talked about how we're passing in the request URL to the actual object.
Well, here we have a switch that goes through and looks at what the actual path was there.
So what this means is if the path was to increment, I will increment my current value and then put the value into storage again.
If the case was decrement, I'll decrement and put it in storage.
And if it just accessed the current, the path, I'm just going to return the value.
And then regardless of what happens, I'll return back the value.
This response will be returned back up here to this. It will be returned back out to the worker here in response.
We'll await the text piece of it, which will be the count.
And then we'll return it back out to our actual client.
So that's a lot of information. So let's go and actually just see what it looks like actually trying to deploy this out.
And then we'll see what the actual worker itself looks like.
So I'm going to change what I'm sharing one more time over to my terminal.
All right. So I've initialized this project. And just so you can see what the project actually looks like under the hood, there's this package.json file, the readme, licenses and stuff.
And then there's this wrangler.toml file.
This wrangler.toml file is what configures Wrangler itself. And then within the source file, there's that index.mjs file that we were just looking at.
So let's quickly look at wrangler.toml. All right. So what are we looking at here?
We have this project that we've named counterexample for. We have some information that tells us where we're actually going to deploy out to.
Right here, I'm going to say I'm going to use our workers.dev service, which just lets me automatically get a this is my demo test account.
The sort of durable object-specific stuff is in build .upload and durable objects.
So within durable objects, we have to, if you remember from the index, probably easier if I just share this in a second, you'll remember that there was this env variable that got passed in to my worker.
And within that, I did env.counter in all caps.
This binding right here is how the Cloudflare runtime knows that the environment variable counter is bound to the durable object with the durable object class named counter is what this piece does.
And then this is just I mentioned before that we're using the modules format to actually upload our script.
That's the case here. So we're going to do that as well.
So let me just quickly share my code again. So you can see that piece, what we're talking about here with the env parameter that's been passed in, and then this counter environment variable that's hanging off of it, that was defined in my Rainflow.toml.
And that's how I'm able to tie those two things together.
All right. Back on over to my terminal.
All right. So let's deploy this out really quickly. So what I'll do there is I'll do Wrangler publish.
And Wrangler publish says, hey, publish Rainflow publish.
And that tells Wrangler to actually go out and deploy my code. And I'm one of the things that could happen here when I'm changing my files around is I've defined a new class called counter.
And we want to make sure that that's an explicit thing that you've done intentionally.
Because say, for example, you've renamed your class or you decided to delete a class.
That will change the durable objects that are actually deployed out.
So we have these migrations in place that you can explicitly say, hey, I actually intended to create a new class called counter.
And you have to pass those the first time that you actually make a change.
So I'll go out and publish this. And Wrangler will take a second and go through and deploy out my script.
And one thing you can see is now what I can do is I can run this again.
If I actually I'll do this. If I run it again and try and run a new class again, it's going to complain and tell me that I've already created the class counter.
So I can't pass in another new class. It does require one second. While that's happening, I'll load up my example.
So while this is loading, talk a little bit about some use cases for durable objects that go beyond simple counters.
So we'll talk about a counter today. We'll talk about a chat demo as well.
We've seen a lot of people building low latency multiplayer video games with them, which has been really cool to see.
We've seen people augmenting Cloudflare's own functionalities.
We've seen people building API rate limiters and sort of cool combinations of durable objects with workers TV, which is our database service.
I hit an error. I'll try again. My Wi-Fi has not been the best today.
Okay. Perfect. And so here it won't apply the new class migration because we already have created it.
The class already exists. But if I get rid of that publish piece, it'll actually deploy out the worker again.
While that's happening, I'm going to go ahead and actually show you all what the worker we built actually does, the durable object and the worker we built actually does.
So let me find Firefox really quick. All right. And so here we go. I'm going to counter example 4.breedabout.workers.dev.
And since I'm just going to the root, it's just going to return back the value to me.
And what I can do is I can run increment and my value isn't.
And if I go back to the root again, same thing. And if I go to decrement, the value will be there.
And we don't have underflow issues. So yeah, that's basically how this is going to work.
And so what we can see actually is maybe I'll show some of that initialized piece, how that all works together.
So let's go back over and I'll share my code again.
So remember this durable object that we had with IDA?
That was actually set to negative 1. We'll keep that in mind. And now we'll go back into this code.
And I just quickly need to open up the actual counter example we were using.
And so this is the actual code that we deployed out. And so one thing that's kind of interesting is you can have multiple durable objects.
So what I'll do is quickly get the durable objects with IDB as well.
And so quickly, we'll just see that we can have a second object here.
So we're keeping two different counts now at this point.
I hope I haven't made any silly typos here.
It should be obj2. Cool.
And that should work.
And one other thing we'll do is we'll actually do something special when we go to initialize.
So when we go to initialize our object for the first time, we want to see what actually happens there.
So in our initialize, I'm actually going to return back.
And so the value we can store, we'll do it actually here.
And we'll start returning back.
So in my constructor, I'll have this other piece, which is like this.wasInitialized.
Equal to false.
And what we'll do is right before we return, we'll check to see if this was the first time the object actually ran.
So this.wasInitialized equals true. If we'll store that.
So this .wasInitialized. Cool.
And so in this way, we'll see when actually the status gets initialized or not.
All right. So I'll go back over to my terminal and publish this up. I had one bug.
Maybe more. All right.
And let me switch from sharing. Back on over to this piece.
All right. So we'll just go to the route right now. So what we can see is DurableObject A hadn't yet rolled over yet from the new deploy.
And DurableObject B has actually been initialized to zero. And we know this is the first time the code ran because we see false here.
And now DurableObject A has been deployed back out again.
So the deploys are not atomic. But this was the first time it had been called.
So this was false. And this was true. Because this is the second time DurableObject B had been spoken to.
Refresh it again. They'll both go to true.
Because both have been talked to before. And you can see this again. I'll prove to you I'm not lying.
I can go ahead and redeploy out. I just ran regular publish in my other script.
And we can sort of refresh here and see. And you can see that DurableObject B got reset.
And DurableObject B is back. DurableObject A got reset.
DurableObject A is back. And if I go to increment, I believe we'll increment both.
So we can just keep incrementing both here up until we get to a different value.
And then if I go and regular publish again and deploy back out my DurableObjects again, their state will persist.
Oops, I'm doing increment still. Their state will persist.
But then they'll be reinitialized all over again. So that's sort of how the initialization piece works.
Cool. So that's sort of the basic demo I wanted to talk through today.
I'm actually going to go and share again, share the code that I have here.
And we're just going to quickly chat through a different example.
So we've also published this chat demo out, which some of you may have seen.
This is built on top of DurableObjects. It does two cool things. One, it does live real-time chat between multiple users.
The other piece is it actually implements a rate limiter, which lets you rate limit based on client IP address.
I'm actually going to focus on that rate limiter really quick, because it's a pretty simple use case.
But I think it's pretty cool. So what we've done here, again, you can see this module format.
In this file, we've defined a worker, which I will find for you, which is here.
So this is my worker that actually handles the request coming in from a client and processes it.
And then it passes that off, actually, to our API handler down here, this function.
And this function will actually go out and call the DurableObjects that it needs to.
Those DurableObjects are defined in this class chat room.
We're going to skip over that. There's a different DurableObject called RateLimiter.
And what RateLimiter does is rate limits based on IP address.
So the actual ID of this RateLimiter class is going to be a client's IP address.
And this is really cool, right? Because you're on Cloudflare's network.
You have the ability to know what someone's IP address, right from inside of your worker.
And then you can go and talk to the DurableObject that implements the RateLimiter for that IP.
And the code is really simple. It basically just calculates the time that you're allowed to take an action again.
Every time you take an action, we give you, we make you wait five seconds.
And we provide a quick race period of 20 seconds to start.
So you can make a quick, you're basically allowed to do as many requests you want in 20 seconds.
And then after that, you'll start to get limited.
So you can make multiple actions in a single burst and then make a RateLimiter.
Sorry, in 20 seconds, as long as you don't do more than four or five requests.
So this is all it takes to implement your own custom RateLimiter now.
And because these are on a DurableObject, they'll be synchronized across all different requests that IP might make around the world.
This is a great example use case for DurableObjects, actually, because the IDs for the DurableObjects have high cardinality.
So we talk about how DurableObjects scale.
They scale incredibly well horizontally. Which means if you have a large number of IDs, say even millions of IDs or tens of millions, we'll be able to handle that load because we can divide it up across many different DurableObjects.
An individual DurableObject is single threaded. And so there still can be some issues with needing to program for a concurrent environment.
But only one request executes on a DurableObject at a given time, which means you're generally limited to how many requests you can process on that given DurableObject.
And so you're better off finding an ID or finding some form of very basically be able to spread your workload across many different DurableObjects with many different IDs.
IP addresses are perfect for that. And RateLimiter is kind of perfect for that use case.
So we're sort of running out of time here. So I'm actually going to kind of skip through talking about the chat room example itself.
You can find this if you just Google DurableObjects chat demo. It'll pop up.
I'm actually going to check to see if there are any questions that have come in. I don't see any.
And so I'm going to kind of leave it there. And there's a couple of different resources for you there.
There's the Cloudflare docs. There's this Edge chat demo.
And then there's the Wrangler template, which you can use as kind of a jumping off point to start building out your own DurableObjects and own applications.
And again, if you want to get that, it's just on GitHub. If you search for DurableObject example, template will come up.
Cool. That's all I got for you today.
And if you have any questions or you run into anything while you're working with this, my email is gmckian at Cloudflare.com.
Happy to chat and happy to kind of see what you guys are going to build.
So thanks a bunch. Hi, we're Cloudflare.
We're building one of the world's largest global cloud networks to help make the Internet faster, more secure, and more reliable.
Meet our customer, BookMyShow.
They've become India's largest ticketing platform thanks to its commitment to the customer experience and technological innovation.
We are primarily a ticketing company.
The numbers are really big. We have more than 60 million customers who are registered with us.
We're on 5 billion screen views every month, 200 million tickets over the year.
We think about what is the best for the customer.
If we do not handle customers' experience well, then they are not going to come back again.
And BookMyShow is all about providing that experience.
As BookMyShow grew, so did the security threats it faced. That's when it turned to Cloudflare.
From security point of view, we use more or less all the products and features that Cloudflare has.
Cloudflare today plays the first level of defense for us.
One of the most interesting and aha moments was when we actually got a DDoS, and we were seeing traffic burst up to 50 gigabits per second, 50 GB per second.
Usually, we would go into panic mode and get downtime, but then all we got was an alert, and then we just checked it out, and then we didn't have to do anything.
We just sat there, looked at the traffic peak, and then being controlled.
It just took less than a minute for Cloudflare to kind of start blocking that traffic.
Without Cloudflare, we wouldn't have been able to easily manage this, because even our data center level, that's the kind of pipe, is not easily available.
We started for Cloudflare for security, and I think that was the aha moment.
We actually get more sleep now, because a lot of the operational overhead is reduced.
With the attacks safely mitigated, BookMyShow found more ways to harness Cloudflare for better security, performance, and operational efficiency.
Once we came on board on the platform, we started seeing the advantage of the other functionalities and features.
It was really, really easy to implement HTTP2 when we decided to move towards that.
Cloudflare Workers, which is the computing at the edge, we can move that business logic that we have written custom for our applications at the Cloudflare edge level.
One of the most interesting things we liked about Cloudflare was everything can be done by the API, which makes almost zero manual work.
That helps my team a lot, because they don't really have to worry about what they're running, because they can see, they can run the test, and then they know they're not going to break anything.
Our teams have been able to manage Cloudflare on their own for more or less anything and everything.
Cloudflare also empowers BookMyShow to manage its traffic across a complex, highly performant global infrastructure.
We are running on not only hybrid, we are running on hybrid and multi-cloud strategy.
Cloudflare is the entry point for our customers.
Whether it is a cloud in the backend or it is our own data center in the backend, Cloudflare is always the first point of contact.
We do load balancing as well as we have multiple data centers running.
Data center selection happens on Cloudflare.
It also gives us fine-grained control on how much traffic we can push to each data center, depending upon what is happening in that data center and what is the capacity of the data center.
We believe that our applications and our data centers should be closest to the customers.
Cloudflare just provides us the right tools to do that.
With Cloudflare, BookMyShow has been able to improve its security, performance, reliability, and operational efficiency.
With customers like BookMyShow and over 20 million other domains that trust Cloudflare with their security and performance, we're making the Internet fast, secure, and reliable for everyone.
Cloudflare. Helping build a better Internet.