Build “invincible” apps with durable workflows

Learn With Jason S8E11 May 29, 2025

Making sure apps don’t fail when networks are flaky, APIs have temporary outages, or other problems? Alex Garnett teaches us how durable workflows make apps resilient without overcomplicating our workflow.

Read the transcript

Captions provided by White Coat Captioning (https://whitecoatcaptioning.com/). Communication Access Realtime Translation (CART) is provided in order to facilitate communication accessibility and may not be a totally verbatim record of the proceedings.

JASON LENGSTORF: Hello, everyone. And welcome to another episode of Learn With Jason. Today, on the show, we're going to be digging in it to something that might, on its surface, sound very enterprisey, which is, like, resilience and retryability and all these things that make apps, you know� make sure� it's the sort of thing that� the thing that makes me always dislike an app is when it feels flaky, right? Like, I don't want to have to try things twice, I don't want have to refresh it to make sure it worked. Even for a side project, I think there are really good ways for us to introduce some of that robustness and one of the tools we can use for that is called Temporal, so that's what we're going to talk about today, is an app called� a service� app� you know what? I should probably bring up Alex and talk about it. So, to teach us about it is Alex Garnett, who you may recognize from Web Dev Challenge. How Alex, how are you? It's a lot of deadlines happening right now, so things are all over the place, I apologize to everybody for starting a little bit late but we're here, we're going.
So, Alex, who aren't familiar with you and you work, can you give us a bit of a background on yourself?

ALEX GARNETT: I'd love to. I'm a developer advocate at Temporal. Prior to that, I was a curriculum at Temporal. Before that, I was at DigitalOcean. I love teaching. I love backend tools. You are like, how do I deploy my app, my deployment's broken. I hate my app. That's things, in my past lives, I spent time on so I like making that part of work easier for people.

JASON LENGSTORF: Nice. One of the things I find really interesting about this� historically, when we start talking about the kinds of things we're going to be talking about today, it starts to get extremely complicated extremely fast. Let's say I want to make a database call and based on the results of that database call, I want to update something in another service. In order to do that, I have my app sending an HTTP request, which then needs to respond to some data, I have to send another HTTP request to the service and that has to come back. If somebody's trying to use this while they're in the car, they switch cell phone towers, you get that unlucky moment, you're in a train. That causes the first round trip to work, but the second round trip to fail. In order for me to try that again, historically, you'd start talking about things like queues, Kafka queues.

ALEX GARNETT: Everything is queues on top of queues.

JASON LENGSTORF: Immediately, I've checked out. That's for a company bigger than mine. For people who maybe heard me say all that and are going to close the video, how can we talk about how this isn't just an enterprise concern?

ALEX GARNETT: Thank you, Jason, for framing it that way. Any kind of production scaling that you're going to have to do eventually, you're going to wind up having to solve a lot of the same "not that fun" problems, whether it's failover, fall tolerance, any time you have a transactions, it might get you in a weird, edge casey state. You didn't test it on a flaky subway train because you live in Cupertino. Every app ends up having to solve independently. That's not fun. Nobody likes to reinvent the wheel.
Temporal has durable execution and what that does is it provides an architecture you with build on, out of the gate, and that way you can model all these endtoend transactions that all do different atomic steps throughout and every time your app will go through one of those atomic steps, that will automatically get persisted so it turns your app in to a state machine and every time your app goes threw one of these states, you get that replicated and persisted and if your phone goes off the internet, they can automatically pick up where they last left up. If you have to take something off the stack, you can do that, too.

JASON LENGSTORF: And so an interesting thing about this, in the example I talked about earlier, you hit two APIs. In a database, if you're making changes, you can wrap it all up in a transaction so if one step fails, you can undo all the work that gets done. If I'm calling a database and another service over API, this change has happened unless I write the logic to make this change happens and go back here and unroll it and do whatever the thing is.
Now, this is the sort of thing that, again, makes me immediately go, what you know? That's fine. Every once in a while, it's going to happen. This is something that, again, like, kind of works, right. It just works out of the gate?

ALEX GARNETT: Yeah, that's what we say. It's nice for things to work. People have solved this in many ways. If you're lucky to have one database, more power to you. In the real world, not a lot of applications just talk to one database. If you look at modern SaaS, we're liking at nine different applications as once. You can't really rollback those. Once you scale at a different level, you can't go in and be garbage collecting.

JASON LENGSTORF: With Temporal� this is one of the things that kind of blew my mind. Instead of making these two calls on my own in my app, I put these into a Temporal workflow. When Temporal calls the first thing and it succeed and the second thing fails, if I retry that workflow, it's not going to call this one again. It knows this change already succeeded so it holds the output so it can run Step 2 until Step 2 succeeds? Is that correct?

ALEX GARNETT: Exactly. When you're making that database call, if that database call's already going through, it's not going to go through again so you can code in a way that's both safe and reliable so you can benefit from those automatic retries.

JASON LENGSTORF: Again, this is the sort of stuff that it feels like this is maybe for only really big teams. Again, if you're calling three services� and honestly, who, among us, for a side project right now, isn't calling an auth service or a thirdparty service. You are definitely calling at least two services, right? Immediately you're going to run into this problem with the call to one works and the second one fails. You need a way to recover from this gracefully. I have learned, firsthand, it is very difficult to manually solve these problems. I remember trying to do this at IBM, which is how I learned how to write Kafka.
For me, as a solo operator, like, I can't afford a team. So, we need a tool. Right. And so the fact that these tools exist is really exciting and the fact that I, as a solo operator, am able to sort of approach these things.
But so, there are a million things we can use this for. Really, any step that we wanted to try, if we want to call an API, make a database call. Whatever. But I have a particularlysilly idea.

ALEX GARNETT: Go on. [Laughter].

JASON LENGSTORF: So I'm working on this project. I'll maybe keep some of the details secret. We're going to unveil it at RenderATL. They'll be able to claim the codes and hit the database and check their auth is accurate and come back and it's going to be updating hardware, right. So, we need to make some hardware calls. We need to make some software calls. There's going to be some network stuff, there's going to be some other stuff. All that needs to happen reliably on conference WiFi.

ALEX GARNETT: Yep. I love demoing Temporal on WiFi. I was at Google Cloud and the best was, here's our gigantic AI model, oh, it's taking a second. It went down and it comes back up automatically. It's a beautiful thing to see.

JASON LENGSTORF: I agree, Josh, this stuff is exciting. So what Robert's saying is there's a bit of a learning curve. This is probably the hardest part, if you watched the episode of Web Dev Challenge, you're basically entering something that doesn't have a realworld analog. You can't, like, use some Legos and just show somebody what's happening. It's very much an abstracted concept and so everything you're learning is, okay, you have to bend your brain into shape to think about how this works so I think maybe the easiest thing to do would be to actually look at it and maybe what we can do is just open up the source of this project that I'm working on and just kind of start with a Hello World. Just get something real basic going and see how far we can get on maybe doing one of these� these validation steps or something of� of similar� we'll see. We'll see. [Laughter]. I always feel like 90 minutes is so long and then I'm like, oh, god, we're already out of time.

ALEX GARNETT: Sounds exciting. Let's do it.

JASON LENGSTORF: Okay. Great. Let me take us into� I'm going to switch this over here...and, just get a couple things set up before I share my screen. There we go. All right. And then I want us to be in this view. Okay. So, this is Temporal. But before we talk about Temporal, I'm going to talk  for just a quick moment� about the sponsor of Learn With Jason, G2I. They are a company out to help you hire. Hiring is superhard. You get a lot of noise in the application process. Vetting an engineer is super timeconsuming, it takes expertise, which means pulling engineers off of stuff that could be valuable to you. So, G2I is solving this problem because they are doing the vetting on your behalf. They have live, technical interviews, they are making sure you get the right people into the pipeline for you. I've known the team for a long time, they are actually in the community. This isn't the typical "dear [insert name here" people. They are plugged into the community. They can cover the needs of CTOs, product leaders, whatever you need. Go to g2i.co today. You can discuss what your team needs. If you are an engineer looking for a job, go there and see if you can get into the pipeline to be hired at one of these companies. They're great. I like them a lot. You should check them out. I think you will enjoy your time with the G2I team. With that being said, let's get back to Temporal. This is the Temporal stuff. We've got the link here to the website, Temporal.io. Let me pull up the code, here, so this is...let's see, this is the box. Let me...close all editors here and we'll just kind of close everything down for a second so we can sort of look at what's happening and then make a call on how best to start.
So, um, I have two apps. We're working in a monorepo today. Is this big enough? Let me make this one touch bigger today. So, we're working in a monorepo. The way the monorepo is set up, we have two apps. This one, we ended up scrapping and combining into this web. We've got our headsup display, which is for the person operating the installation and the web app, which is for everybody else. We've got a database, this is Convex, they handle codes, validate codes. Then we have our hardware, we've got Web MIDI, we've got a touch interface that people are going to use, capacitive touch, if you've ever heard of that. We have some locks that need to be unlocked and lights that need to be turned on and off. All sorts of fun stuff there. That's going to be some timed stuff. That's going to be a little bit of "human in the loop" stuff. We've got a long ways to go. So, Alex, if you were coming into this app, which is somewhat functional� [Laughter]. � and you had to add Temporal in, are you adding it as, like, a shared library? Are we spinning up another web app to be an API? Do we want to build it into the API of one of these apps? Um, how would you go about it?

ALEX GARNETT: That's a great question. So what you can do is you can just pull down some Temporal boilerplate as an app framework. I think that will be a pretty useful way to do it so if you want to get a new directory� I'm going to give you an NPX command just so you can take a look at it right here.

JASON LENGSTORF: NPX.

ALEX GARNETT: I need to connect my YouTube to comment.

JASON LENGSTORF: You can put it in the private chat. This is going to give us...this is going to give us a Temporal app. So that's running now.

ALEX GARNETT: Whoops!

JASON LENGSTORF: NPM has failed.

ALEX GARNETT: We can do it differently.

JASON LENGSTORF: You know what? It shouldn't have NPM'd.

ALEX GARNETT: That's okay, we can do it a different way. You know what? We're going to try it a little bit different. We're going to clone down a different repo. Start by cloning this down in the directory.

JASON LENGSTORF: Okay. So, we're going back here. We're going to go "get clone." And, that...

ALEX GARNETT: All right. Hop in there�

JASON LENGSTORF: Hold on, this one worked.

ALEX GARNETT: It did. It just said it failed?

JASON LENGSTORF: You know what? I bet it just failed because we didn't have� let me just run the NPMinstall here, now it's using NPN, and that should let us�

ALEX GARNETT: Looks like it's happy now.

JASON LENGSTORF: So to do this, we've got our�

ALEX GARNETT: It's just the one SDK library you should have to grab. What that does, it gives you start commands so you can deploy your worker. Those dependencies, that's all you would add to a different project. You've got Temporal Activity, you can pull that in.
Let's take a look at the skeleton of this for the start. Go up to the Source Directory and for starters, open that workflow.ts. This will show you the scaffolding. We're giving you a workflow to wrap your entire function in. It returns a promise. It's totally empty, we can do whatever we want in here.

JASON LENGSTORF: So the magic here, then, is a workflow is a function. The function accepting some input and returns and output and a promise? And as long as that's true, we can do whatever we want inside of it?

ALEX GARNETT: Precisely. That's going to wrap our entire application structure. Take a look at activities.ts. Just a function. We separate these because our Python SDK does some cool sandboxes where it passes through imports. You don't have to have them sandboxed like that. You got workflows, you got activities. All it's going to do is call them.
Take a look at worker.ts real quick. This is what actually runs the Temporal code where it's deployed and so in a proper deployment, maybe you've got a Kubernetes pod, you know, full of workers. They all just run different Temporal jobs as they come in, they can pick up workflows and activities. Usually you don't have to mess with a worker definition at all. You can take the boilerplate that's running somewhere and it's going to work. We talk a lot about edge deployments with the conference booth, and I think that's a really cool model because I'm running the Temporal dev server locally on my machine and running one worker. There's no obstacle to doing that. You can actually run part of the Temporal service locally. You can run it at edge, depending on what you're worried about going down on you.

JASON LENGSTORF: My trick is, lots of people will be using their phones to kick things off. If I'm running it locally, can the deployed web apps on other people's phones 

ALEX GARNETT: What you could do then is one of your services locally and have that tied to the box you're deploying, that way you know you're never going to lose connection.
Why don't we now just kick off a Temporal dev server just so we can see that running. Do you want to do that? Do you have Temporal installed on here?

JASON LENGSTORF: I think so. Let's see...yes.

ALEX GARNETT: Cool. You can run it from any directory. Temporal Server.

JASON LENGSTORF: Let me start one over here. Temporal Server.

ALEX GARNETT: Space. Startdev. That's it. 8233 like that, so you can take a look at it in a browser window.

JASON LENGSTORF: Okay.

ALEX GARNETT: Here is the Temporal web UI. So you've got a Temporal service running. A really cool thing is that our commandline client, which you can normally use to interact with a remote instance, can run a whole server out of the box. We really like this feature. If it's running on one box, that's fine. If you want a bigger one, we've got Helm charts, Docker. Right here, you've got all the component parts of a Temporal service. It's running back by SQLite. You have enough to work without of the box. You deploy that worker code and all you would have to do is then just write workflows and activities and you're off to the races. So�

JASON LENGSTORF: Okay.

ALEX GARNETT: Now that we've looked at the server, the working code, the boilerplate, let's add the dependencies.

JASON LENGSTORF: Let me show you the two apps I have. I'm going to delete this other repo before I forget why it's there. This is what most of the people on the conference floor will be loading. It's a TanStack App. We are using TanStack Router. Actually, if I can get back out here and PNPrun dev, restart everything. That's our Convex running. This is the TanStack app. So, this is� the first app, here, so we go to our home. And I'm going to need to...uncomplete this activity so that I can...can I do that? You know what? Why don't we just add a new one, because that'll be easier than me trying to do this again.
So we're going to, instead, just add a new activity. This activity is going to...okay. So that goes away and this is going to be...New Activity. So, we drop that in. And out here, because we are� what?! So, out here�

ALEX GARNETT: It's very exciting. I'm seeing VIP ticket. I'm excited about whatever this is.

JASON LENGSTORF: This is still proofofconcepty. You'll get logged in. You'll create your account� I'm logged in with Clerk so we've got the kind of Clerk thing going on. It's going to give you some instructions and at the point you've completed them, you will do something, like upload or a photo or whatever the thing is and then you will find an attendant that will validate and they do that by scanning this QR or if you're an admin, you can say, they did the thing or they didn't do the thing. What we end up with, out here, is you have your completed activities and then if I go to the� we got our audit log going here. The admin, we've got the prizes we're giving away and all�

ALEX GARNETT: Giving away a Switch 2, very nice.

JASON LENGSTORF: Well, if we can get one. [Laughter]. But then we've got the dashboard, right, this is where the other app comes in and this is the one running inside the app� this one's running inside of the box and so this one's running at...localhost...4321. And so, this one, I don't have my, um, the hardware that would make this work with me, that's at home. But what I can do is when I� when I put these into Box 1, you'll see, it's kind of updating and so this is what the box operator sees. So, my hunch is, this is probably the one that should have the APIs because this is the one that's sort of the core brain.

ALEX GARNETT: That's my guess, as well, but actually, you just showed something fascinating. Go back to when you were talking about activity. I think the context you brought this up, the activity you're going to have somebody do on the conference floor, you said upload a photo. I don't know if this is where you were thinking about having Temporal. Your box will stay up, whatever. A cool thing I'm seeing right here is, we love to talk about using Temporal to model any kind of endtoend transaction. One person's interacting with this box, you got to track their progress through this activity and at some point, it's going to get signed off on or not. We have some steps. We have a nice endtoend process.
The way we often model this for financial transactions, where we have to get from Point A to Point B, that's a workflow. You've got a nice flow chart right here.

JASON LENGSTORF: Yeah. And this may not be exactly right but what we were trying to come up with, is we've got two pieces. There's code redemption and activity completion and so what I was picturing is three separate workflows, one of which gets triggered by a parent workflow so the way I'm thinking about doing it is when you go to do an activity, you would say, I want to do an activity, I want to kick off a workflow and that's going to do whatever the thing is. It's going to say, okay, you need to put in this information or, you know, like, one of the ideas I had was, we're going to hide QR codes around the venue and if you scan five of them, that's going to end the activity. At which point, the activity goes to the next phase, which is complete. So it has some kind of a validation step that says, here's how we know whether or not this thing is complete. That returns a success or failure. If the� if we get a success, then we open up the code generation workflow, which would go off to the database, say, create a new workflow for this user, attach a random prize to it, you know, make sure that the prizes are in stock and hold them in inventory, all the things that we need there and then bring back the code for the user so that they� in their app� now have a list of their claim codes and then up on this side, we have� this one's a little more complicated. The attendee will enter their code, that's, like, manual on a keypad. The workflow gets started when they submit the code. Like, "press enter" kicks off the workflow. That'll turn on some lights in the box, at which point we have to hit the database to see whether the code is valid. If it's not valid, we need to flash the lights. If it is valid, we need to update the interior prize screen and say which prize goes into which locker because there are multiples. We have a "human in the loop" step where the person operating the box has to load it and confirm it's actually there, which triggers the lock/unlock, which pops the door open, then we have to flash some lights. The person will actually remove the prize, at which point, the person inside of the box will make sure the door's locked, pull it closed, and hit the "complete" button and does some light things and updates the code as, this one's been retrieved, and then loops us back to the beginning, right. So there's a lot that needs to happen and that's why I was thinking that Temporal would be great for this. I have to talk to the hardware on the device. This is multiple Arduino and Raspberry Pis.

How many are you going to have? It can just trigger a GPIO, whatever, to unlock a latch. The escape room design is basically that, over and over again. How do I get from a web request to a hardware lock? It's� it's not complex, but just so many people don't think about hardware that way.

JASON LENGSTORF: Yeah. We're doing Web MIDI, we're coding this MIDI address as this key. You know, key code so we have 0 through 9 and then enter/cancel and we can send Web MIDI for the success, pending and failure lights, as well as the lock. So it's all Web MIDI, which is great for me because the API is super straightforward. And from a workflow standpoint, then the way that this ends up working is, it's� it's just a series of API calls, like, I call the Web MIDI app to send a number or receive a number. I call my database API so that I can update or retrieve or whatever and if we just kind of patiently string each of those steps together, none of these steps, in isolation, is hard. It's only hard when you think somebody's going to punch in a code and the door unlocks and there's a prize.

ALEX GARNETT: Are you familiar with "phone freaking"? Have you ever heard of that?

JASON LENGSTORF: No.

ALEX GARNETT: This is apparently how Steve Wozniak got started. This would have been the late 70s, people figured out how to hack the phone system. If they could make the sound of an eighttone or a sixtone, they could actually tap in to what should have been, like, a closed circuit. If you're looking, like, Web MIDI, that's just a series of tones, they're hardcoded at different intervals. It is totally possible that you can have, you know, somebody in the loop, hacking your Web MIDI system.

JASON LENGSTORF: Yeah, this� we were trying to think through some of this stuff, too, because how do you make it so that if somebody gets a code, they don't just, like, tweet the code and everybody comes in� so every code is generated on a purseuser basis, they're globally unique, they're single use and all of that is more steps we have to perform, which is why this starts getting� it starts feeling very overwhelming. This is not a small amount of things because each one of these things is a reasonably significant piece of business logic so it's stringing all this together in a way that is resilient. If this one screws up, I don't have to worry that all of this has just broken this code and this will never happen unless I go in and do it manually. I don't want to have to sit at this booth all day, babysitting it, I want to trust it will run.

ALEX GARNETT: I have one more comment while this is on screen. The fact that you have called this Activity Workflow is going to make every Temporal person cry. An activity is all the components parts run by a workflow and so just saying�

JASON LENGSTORF: So we'll have to rename this a task? Okay, so we'll call this "task" so it doesn't melt everybody's brain here. Naming things is tricky because I also had a problem where it's like, we've got the box and" conference attendees." Words are extremely hard.

ALEX GARNETT: We're saving lives here. You know. Not all heroes wear capes is what I can tell you. It's very important to be able to disambiguate.

JASON LENGSTORF: Where can we learn more about the app we're building? Once we go live at Render, I will do a little more of a deep dive on what this is and how this works and probably a bit of a postmortem of what I could have not spent time on and what I should have spent more time on because I'm sure that's going to be the case. But right now, it is very much this� this stream is going to be the most context you can get on it. [Laughter]. Alex was made for camera. I agree. [Laughter]. Okay. So, to make sure we have plenty of time here, because we got about 45 minutes left, why don't we get� let's get rolling here. So, you said, go into...we need...the dependencies.

ALEX GARNETT: Yeah, out of that package JSON, grab all those and those are pretty reasonably recent. Go ahead and grab all those, you'll be happy with those.

JASON LENGSTORF: All right. I'm going to throw these in here. I'm going to stop this. We're going to npninstall again and then we will pnpdev again. I hope the leaf blower in the alley is not coming through.

ALEX GARNETT: My cat's been trying to get out for the last 20 minutes. Streaming's always very dangerous.

JASON LENGSTORF: I've got filebased routing here. I can make serverside routes or clientside routes depending on what is easiest. My guess is, we want it to be serverside?

ALEX GARNETT: Yeah. So, this is a great question. What we use in Temporal, our primitive for sending any input to a running workflow is something called a signal and you can send a signal from our commandline client and you can send our signal just by pulling in a tiny bit of the Temporal SDK guts and giving that application the abilities and Temporal client functions, like signals.

JASON LENGSTORF: And so my hunch here is what we want is to have people send an API request, basically? Hit an API endpoint, that'll kick off the flows?

ALEX GARNETT: Yeah. And so one of the things you use the Temporal client for is to kick off a workflow at the start so what you can do is give one of your applications the ability to kick off the workflow and send signals into a workflow once it's already running.

JASON LENGSTORF: Okay. Do you use one endpoint and route internally or do you set up separate endpoints per thing?

ALEX GARNETT: It's all gRPC. We talked about� at the very beginning of the stream� everything becomes queues. All the Temporal SDKs work over gRPC. You have to expose [Indiscernible] but the really nice thing about that is everything that your serverside network is getting is being sent over gRPC calls.

JASON LENGSTORF: Structurally for the web app, how would you architect this? Each one is doing a different thing or are you creating "/API/Temporal"?

ALEX GARNETT: That's a way to do it, /API/Temporal.

JASON LENGSTORF: So I'm guessing the way that this will probably end up working is we're going to have, like, an activity that� or a task that somebody's going to start and so they would� upon doing that, they click that button. That button is probably going to hit/API/ID with an activity ID and a user ID.

ALEX GARNETT: The fact that you said "activity" again, I'm�

JASON LENGSTORF: I'll retrain my brain.

ALEX GARNETT: I've got this AirPod to not even hear "activity." [Laughter].

JASON LENGSTORF: We want the ability to have somebody kick off a task. We'll make the API/task endpoint, which is going to accept a task ID and the user will call it with their clerk ID. There will be some� some headers or something we can get.

ALEX GARNETT: That sounds good to me. Want me to paste in here for you some sample code or I can tell you how to get started?

JASON LENGSTORF: Why don't you just tell me how to get started.

ALEX GARNETT: Import Connection and Client, in curly brackets, from Temporal/client.

JASON LENGSTORF: Okay.

ALEX GARNETT: All right. And then you're just going to want to do, "async function run" here.

JASON LENGSTORF: The way these ones work is we have to export async functions. These are serverless functions so we export the method. And so this will� this will get run whenever somebody, like, hits this endpoint.

ALEX GARNETT: Hits the endpoint. Okay. Cool. Let's do "constconnection=await." We're going to do an "await" here, yeah. We can change it. "Connection.connect." Should be a method. And that's going to go in brackets, so, some pseudo JSON to address and that's going to point at wherever your Temporal address is. If it's running locally, you can say "localhost." If not, you can do an environment variable.

JASON LENGSTORF: Let's do an environment variable just because that's going to make life a lot easier. We'll leave the client public for now. There's nothing sensitive here. We'll go Temporal URL. That is going to be this one or this one?

ALEX GARNETT: The gRPC.

JASON LENGSTORF: With or without the protocol?

ALEX GARNETT: Without.

JASON LENGSTORF: All right. I'll stop and restart to pick up that environment variable. And then, that means in here, we're going to use the Temporal URL, which will come out of the Astro environment and then what else?

ALEX GARNETT: All right. So now you've got your connection object, so now we're going to create a client object based on that.

JASON LENGSTORF: Oh, we don't need anymore. [Multiple people speaking at once].

ALEX GARNETT: "Const client" and that's just going to be New Client with the object passed it to. Now we're going to show what actually kicking off a workflow from one of these client looks like. Okay. So, you're probably going to want to put that execution in an object, so we're going to do const here. You don't have to pass it to an object. We're going to say "my task workflow," or something like that.

JASON LENGSTORF: Okay.

ALEX GARNETT: And we'll do an "await" call for now. Client.workflow.start.

JASON LENGSTORF: Okay. And that's going to take our workflow function, which I imagine we're probably going to declare somewhere else.

ALEX GARNETT: Yeah. Exactly. And so with that client, you're able to dispatch a workflow. You can pass arguments. We'll leave this boilerplatey. Anything you need, if you don't want that on the Temporal side, if you want to bootstrap it, you can do it right there. But, basically, that is all you need to then just kick off a workflow for the very first time and then that workflow will run with whatever you bootstrapped with it.

JASON LENGSTORF: I'm going to stick them all here, in this lib folder. Then we want workflows. And so my export function, task workflow, and that is going to return a promise with something. We'll figure that out later.

ALEX GARNETT: And you're going to want to do some imports at the top, so just here, based on what we already had you grab. You can stick that in the very first line of the function. That way, you've got all your calls pulled in so you know how to properly decorate.

JASON LENGSTORF: Grabbing that.

ALEX GARNETT: Yep.

JASON LENGSTORF: Okay. So we've got proxy activities logged, define signal, set handler, conditions. And we've got "application failure." What are you mad about?

ALEX GARNETT: We might have missed "comment" as one of our imports. Yeah, we did. Go back to the package JSON. Grab "comment" and it's the same version string.

JASON LENGSTORF: Okay. Oops...okay. So, now we've got those. Um, and so this like, the basic you were talking about where it's just a function that returns a promise.
Where does all this come in? Like, what are we getting out of all this?

ALEX GARNETT: These are basically all the things your workflow can do to respond to input or send errors. We have some endpoints that are going to send data to our Temporal application. The first endpoints is kicking off the workflow. For any "human in the loop" functions, what other workflows, those are all handled through signals and so what I imagine you'll wind up doing, every other endpoint you have is going to be sending signals to your running workflow and so part of what those "define signals/set handler do" is allow them to respond. It sounds like I'm making an API call. When you send a signal, that is serverside and gets logged and propagated down to all your Temporal workflows. Our signals are ridiculously robust. It seems overengineered until it's not. The way we have all these endpoints built out is those can all get persisted and logged against any workflow.

JASON LENGSTORF: Got it. Cool. So then, maybe the� the way to make sure we get through at least the conceptual stuff is let's hardcode some values but add a "human in the loop" thing here and maybe add an API call. So maybe what we can do is, let's start. We'll get a workflow going and then let's have an activity that loads� I don't know� our active inventory. And then we'll have an activity where we have a human hits an endpoints, we can see it's a button, we'll probably do it through Curl.

ALEX GARNETT: I have some ideas about how I want to do this, this sounds really cool. We just looked at your client route. Why don't we do one more client route. We can have that send a signal. I think that will a nice way to define one more endpoint.

JASON LENGSTORF: Okay. Let me duplicate that.

ALEX GARNETT: Duplicate that, exactly.

JASON LENGSTORF: I'm just going to call this "validate" and we'll do whatever later.

ALEX GARNETT: We're no longer starting a workflow, we're going to be sending a signal. We have to get a signal handle first. If you already have the context from the same client, you can just already have a handle from that same workflow instance. If you have to then get the handle from a running workflow, you can call "get handle." "Constsignalhandle=client.workflow.gethandle." Then you would pass it, the name of a workflow that you know is going to be running with a particular UUID.

JASON LENGSTORF: Right. And so we can� we can probably fake that, for right now, can I manually set the UUID when we start the workflow?

ALEX GARNETT: Yes, you can.
[Multiple people speaking at once].

JASON LENGSTORF: To prove how this works, let's just do this�

ALEX GARNETT: And then one more line. You're going to call that� "awaitsignalhandle.signal." We're sending the name of the signal that we want to� that we're going to declare in a second, so validate. Exactly. And you can pass additional arguments to that. You can have signals take names, they can take arguments. Now we have two different endpoints. We have one endpoint we're using to kick off our workflow in the first place. That's going to inherit some amount of information. Now we've got a dispatch, we've got it running. It's registered serverside and one more client that's actually going to send a signal. You can do a console log and return that here.
Here is our simple our routes need to be, they need to have enough Temporal boilerplate so they can hit the server and do something. Pretty much, this is what our routes look like.

JASON LENGSTORF: Oh, you're mad because we don't have our actually workflow yet, but we need to finish this workflow first.

ALEX GARNETT: We'll get there.

JASON LENGSTORF: We got a signal. We got a task.

ALEX GARNETT: Before we define our workflow, let's actually write an activity. Activities are more or less pure functions and they get called by our workflows.

JASON LENGSTORF: Async function. The first one is going to be "load items." And that's going to return promise� you know what, let's let it just infer. I think that'll be fine. And then we're going to return to start just an array of the prizes that I will grab out of here.

ALEX GARNETT: Yep.

JASON LENGSTORF: And that'll make life way easier. I would like to get in here and actually make this call, but we're not going to stress about it right now.

ALEX GARNETT: The nice thing is, activities, you're just doing functions.

JASON LENGSTORF: Okay. So we've got our bits here and that's just going to be returned by loading the items. This� we'll mark it as async so it'll automatically get wrapped in. Anything else with this one?

ALEX GARNETT: There's nothing else you really have to do, out of the box, to mess with your activity. One thing, at some point, is how much data are we passing back and forth from workflows to activities? A cool part about Temporal is depending on how much data you're actually needing to read and write as part of a Temporal workflow, you don't really need to add a separate database. Where you'd normally be writing to Redis, you can pass data because everything that goes into a workflow is durable and is effectively persisted and so, it's a really neat feature and we do put, you know, like, some threshold on, like, how large� like, you don't want to just pass enormous binaries. If you're passing some JSON, like most web apps are, you're not going to hit the threshold and that makes Temporal really neat, is you can engineer the database calls out of your application.

JASON LENGSTORF: Okay. Okay. If you are feeling like these concepts are steep, please stick with us because when you actually see this operate, it starts to come together. There's a little bit of boilerplate here, but that's for a reason because we need these to be atomic and welldefined so they can be automatically retried and durable so don't give up on us just yet.
Robert� you want to take it now?

ALEX GARNETT: Yeah. So, a really good point Robert is making is, okay, we're now letting our web app directly send signals with a Temporal client to the Temporal server and, yeah, how does that security model work. We have an open gRPC endpoint. I don't know if you've secured it before, most people do not build additional SSL stuff. You can do it. It's not that widely done. Most of how we would do this, at that point, depending on how you're engineering and deploying your Temporal app, is the best way to do it� in most production deployments� is a lot of wire rules. You've got one client can make these calls based on where it is. We encourage people to just use protected routes because SSL gRPC is kind of gnarly�

JASON LENGSTORF: So what we're doing is we're basically making a backend for frontend. We're standing up an API. You have to be able to hit that API, which I can authenticate over usual OAuth stuff. You need to make sure that client is only accessible to somebody who is properly authed, so under a protected route.

ALEX GARNETT: Great question. We appreciate that.

JASON LENGSTORF: So let's see if we can get this thing going. So we've got our first activity, which is going to be loading the items. And then our second activity is going to be validate, which I think we can hardcode a true/false, yeah?

ALEX GARNETT: For now, we want it to just return something.

JASON LENGSTORF: Let's have it return true and later, we can have it return false to break it.

ALEX GARNETT: All right.

JASON LENGSTORF: Okay. So, these are our activities. The first one is to load items and second one is to see if they did what they were supposed to do through a "human in the loop" check. They have to hit the button that says, "yes, this thing is valid."

ALEX GARNETT: We're doing great. We have our client stuff done and we know how to signal them. We have our activities written. We are pure functions our workflows are going to call. We can now go ahead and, like, put our workflow definition together.

JASON LENGSTORF: Let's do it.

ALEX GARNETT: We're getting there. One thing you'll want to do is put together some retry rules for your workflows. That way, you know how you're going to be calling some of the different activities that your workflow is going to call. And, so, let me see... before the actual workflow function, so in between the imports, let's just do, uh...what's the best way to do this? Hang on a second...in our workflow definition, I think when we first kick off our workflow, are we going to be waiting for input right away?

JASON LENGSTORF: Our workflow starts and immediately loads the items and waits for the validation step and then it'll complete.

ALEX GARNETT: That sounds good. It's going to run an activity.

JASON LENGSTORF: It'll run two. It runs one, waits for it to complete.

ALEX GARNETT: We'll run the first one. We'll wait for a signal and run the second one. Sound good?
Excellent. First, you can set up any variables or any bootstrapping you have to do but then what you can do is literally just call that function.

JASON LENGSTORF: Okay. So, we're� what do we call this one? "Load items."

ALEX GARNETT: "Load items." Throw it in a variable. You can wrap that in a "try catch", if you want. Temporal is really good� the cool thing about writing with Temporal is one of the things we do is, we automatically replace a lot of language functions with durable versions of them and so one of our favorite examples is, like, a sleep call. Normally if you call "sleep," it's going to way three seconds. In Temporal, if you call "sleep," then that actually dispatches a timer up to the service and if your client goes away while they timer is going down, nothing will happen. It will wait for another client to come online and inherit. So, we basically make a lot of your code a lot smarter and durable�

JASON LENGSTORF: So you talked about a "try catch." Are you doing something that will automatically "try catch" this?

ALEX GARNETT: We do automatically we do have a "try catch", yes, kind of. If you don't do a "try catch", you will get the benefit of our retry policies and we're going to define those right now. So, up out of this function, out of this workflow block, let's do "constant" and we're just going to put, in brackets, the name of your function call, so, "load items," but in brackets. It could be a list. Curly bracket, I'm sorry.

JASON LENGSTORF: Got it.

ALEX GARNETT: Because all of our activity calls are going to go here. We'll do two of them.

JASON LENGSTORF: Hey, you're good at doing this live. [Laughter].

ALEX GARNETT: That's going to equal proxy activities and then a left karat and that's going to go, "type of" and then "activities." And then right caret, and define some cool Temporal constants. We have "starttoclose timeout."

JASON LENGSTORF: Starttoclose timeout.

ALEX GARNETT: You're going to set that to five seconds.

JASON LENGSTORF: I was about to say, okay, is this milliseconds.

ALEX GARNETT: We like to make some things easy. You can also optionally set a maximum interval for retries but you don't have to do anything else right now. That can be enough.

JASON LENGSTORF: We've created a naming collision here.

ALEX GARNETT: Yes, we have.

JASON LENGSTORF: Do I need to make this "activities" instead?

ALEX GARNETT: We usually do "import type asterisk" as activities from activities.

JASON LENGSTORF: Which one of you�

ALEX GARNETT: Asterisk. There you go. That's typically the convention we work with to fix that. What this does is�

JASON LENGSTORF: Hold on, what am I doing wrong? Why is this mad? Cannot find� can't spell. Can't spell. That is a lot of the problem happening right now. Got it.

ALEX GARNETT: This is like the automatically "try catch" we were talking about so if you don't wrap that call in "try catch", everything will automatically have the ability to get rerun if it doesn't return successfully or if it doesn't return within five seconds. Out of the box, that will allow all your activities retry, which is cool some of the time but it not be what you want. If, for example, you're trying to fail on bad data or trying to fail on, like, actually a failure case as opposed to just an outage or something you want to be able to retry automatically, that's when you'd want that "try catch", right. You don't really need it here because we're not thinking about a way we can refail this. I hope that was a cool way to reframe that so you can kind of see, what's an automatically retry and how to handle bad input.

JASON LENGSTORF: Got it. Got it.

ALEX GARNETT: Now, between our two activity calls, we're going to handle that signal. Right. So, we're going to call� let's do "set handler." I think the h� yeah. It's going to be the name that you want to give that signal.

JASON LENGSTORF: Did I already call this something? Let's see...no, it was...validate?

ALEX GARNETT: Yeah. So you can just call it "validate signal."

JASON LENGSTORF: You're giving it, like, a�

ALEX GARNETT: It doesn't have to� where's the name set? So the name that actually gets set would be "fulfill order signal." Yeah, give it the same day.
"Is validated." And then you can assign that, equals caret." Let me send you boilerplate for how we usually do it. Copy that in because that's how we normally do it. And then, basically we're going to do...this, right here.

JASON LENGSTORF: Where's that signal coming from?

ALEX GARNETT: That's the client that you set up to run this signal. Right.

JASON LENGSTORF: Did we set up a client to run this signal?

ALEX GARNETT: That matches the string name, in effect.

JASON LENGSTORF: Yeah, but how is it�

ALEX GARNETT: Sorry, we have to export up top. You're absolutely right. Throw this in outside of the workflow. We're going to change the name afterwards. Before the workflow definition, throw in that export const. Yeah. Right there. All right. Cool.

JASON LENGSTORF: And so this is going to be "validate signal." And this is going to be "validate." And then this will be "validate signal." And then we get back whatever we want. And so, "is order fulfilled." So all of this is whatever I want it to be.

ALEX GARNETT: You don't need "order." You basically just need a variable you can wait to be set to true.

JASON LENGSTORF: Okay. "Is valid" I can leave out then and we can set some variable?

ALEX GARNETT: You can set some variable, yeah.

JASON LENGSTORF: Like that.

ALEX GARNETT: Exactly. Bingo.

JASON LENGSTORF: Why are you mad? Oh, because I can't spell. Jeez!

ALEX GARNETT: There's worse problems to have. [Laughter]. "Await signal processed is true."

JASON LENGSTORF: This is a function.

ALEX GARNETT: You can do a little [Indiscernible] parenthesis.

JASON LENGSTORF: And then we're going to do one of those. And after that, we have the second thing?

ALEX GARNETT: After that, run your second activity.

JASON LENGSTORF: Oh! That needs to be awaited and then we're going to...okay. Nice.

ALEX GARNETT: We're missing some logging, obviously, and we're just returning some strings, here and there, but that is a workflow that contains some signal in it.

JASON LENGSTORF: Okay. Just to show this is working, we will give them a...items.atzero.name. So that way, we'll get a prize listed. Right? So now we've got a task that kicks off. We've got a workflow to handle that. This loads the items. It'll end up being from the database, but for now, it's just hardcoded. We then wait for someone to do a thing and we are� we're wanting someone to validate a signal. This "await" just pauses the workflow. When we say "pause," it doesn't mean it's spinning indefinitely, it does [audio cut out] dormant, I'm not going to do anything until the signal arrives, at which point, it will wake up, run this call, come in here and return a final value.

ALEX GARNETT: Exactly. All the clients are stateless. Any one of your clients can go away. Your workers can crash. Peoples' phones go offline, all the state is tracked serverside. It'll get dispatched to that new client automatically. It's not blocking, it's saying, I have no work to do for this workflow until I get the signal coming in from somewhere.

JASON LENGSTORF: With the log, do you just� do you just, like, throw whatever you want in here?

ALEX GARNETT: Yeah, so one of the cool things is� I was talking about how Temporal quasidecorates your code in the background so your sleeps will turn into timers on the server side. Our [No audio].

JASON LENGSTORF: Did you just freeze or did I just freeze? Oh, no.
If Alex is out, we're cooked, y'all. [Laughter]. Let's wait for Alex's stuff� okay, Vanessa can hear me. You know what I didn't mention, y'all, is we have captions. I don't know why I didn't talk about this already. But we've had Vanessa here, from White Coat Captioning, doing the thing all day so if you want to go see the captions, go check them out. That is a� done by a real human. Feels rare these days. But it is delightful.
So, let's do� let's do this. Let me see if I can make this thing work. So I'm here, I've got my API. I'm going to go to API Task and that's a "get" request that has no arguments so theoreticallyspeaking, what will happen� this is a 10,000foot "if." I'm going to run this and it's going to do nothing because I...why wouldn't that work? Did you die? "No API route handler." I'm going to look up what is going on with this. Astro endpoints...oh, man, this is going to be an exciting ending because we're going to find out whether I remember how code works at all. "Return new response." "Export function." Yeah, dude, I did this. "Export async function get." Yes. "Return new response." Oh, oh, we didn't actually do the workflow so I need to bring in this workflow, which is called "task workflow." So if I come back in here and I do one of these and then I do one of these...you're not happy, expected two arguments. First argument is... what's� an argument for options was not provided, which I'm going� no, can I not get away with that? Workflow ID. Oh, right. This is one I'm going to say "ABC123." Don't give me that. Workflow start functions. Task queue...I think that was called "default." And my reasoning for thinking that is in this� Oh, Alex is back. Thank god.

ALEX GARNETT: Exciting cliffhanger. I missed whatever you were doing for the first 90 seconds, but that ruled. I was about to say, the one parameter we're missing to be able to kick this off is just defining what task we're going to run this on and we need a worker, basically. The one thing we haven't done is supplied a worker for this app. You can copy that from the boilerplate I had you clone down earlier because a worker is pretty much boilerplate. It's Temporal.
Workers are basically, in production, meant to run in Kubernetes pods. But what you can do if you're running a oneoff, you can deploy them on the commandline. One of the things we need to define is the workflows and activities it is allowed to run. Because, think about it, if you've got a big Temporal deployment and it's running workflows and activities, the way you allocate capacity and the way you handle load is you deploy more pods of workers to run certain activities or certain workflows on certain tasks. You want to make sure� we usually use a shared constant so you're not just taping it in multiple places. Let's make sure we're running everything on the same task queue.

JASON LENGSTORF: Does it matter? Can we call it "task queue"?

ALEX GARNETT: You can call it "task queue." We're telling it we can run all of our activities so we scroll down and you see, "activities" is here. It should also be allowed to run all the workflows so this is a decent way do hardcode that. If we're not doing more finegrained, what are you allowed to run? Okay, it's good.

JASON LENGSTORF: Okay.

ALEX GARNETT: So as long as that task queue variable matches, I think you get the task mismatch here. You got "default" in one place. You would be surprised how annoying that is.

JASON LENGSTORF: I think this caught Nick in Web Dev Challenge.

ALEX GARNETT: We've added in the web UI, hey, you have no workers running. What you want to do is kick off that worker. That worker should be running somewhere.

JASON LENGSTORF: Did I need to set up that task queue somewhere?

ALEX GARNETT: No, it gets instantiated.

JASON LENGSTORF: That did a thing.

ALEX GARNETT: You want your Temporal server running, which you already have. You want to run your worker. Usually, we can do that with npmrun start.

JASON LENGSTORF: It did a thing.

ALEX GARNETT: See where it says "ABC123," we have a running workflow. This is exactly where you should be. This is telling you, you have a workflow dispatched. Temporal's working, it just doesn't have a worker ready to process the work yet. All you have to do is run an instance of that worker.

JASON LENGSTORF: For me to run this, is it a Node process?

ALEX GARNETT: Yeah, it's just a Node process. What we would typically do is assign npmrunstart. It looks like this, it can also run Node [Indiscernible] and we would just assign that to a start and that way, you just have the worker running. So, it should work to do "TS Node."

JASON LENGSTORF: Oh, I don't have TS Node.

ALEX GARNETT: If you have Nodemon, it should work?

JASON LENGSTORF: I don't think I have any of those installed locally. What do you mean, that doesn't work? Okay. Let's do...now we're fighting. [Laughter]. Okay. So, was it just "brewinstall"?

ALEX GARNETT: Go back into our package JSON and� yeah. We're good here.

JASON LENGSTORF: Okay. So we're going here.

ALEX GARNETT: Add a dev dependency block.

JASON LENGSTORF: TS Node and I'm just going to star it because, YOLO. Npninstall and then now, if I NPX...

ALEX GARNETT: You should be able to run TS Node, asis, go back up through your command history.

JASON LENGSTORF: We're going to run TS Node and it's still not working because...

ALEX GARNETT: Oh, you know what? You might need� no, that shouldn't be necessary. It should run, asis. This is my leastfavorite part of trying to bootstrap.

JASON LENGSTORF: Module not found. Cannot find module Temporal activities.

ALEX GARNETT: Oh, it's just the import path. Where's our worker, relative to our activities?

JASON LENGSTORF: Should be right next to it.

ALEX GARNETT: Should be right next to it, right?

JASON LENGSTORF: That is right where it's supposed to be.

ALEX GARNETT: That looks like it should be good. Okay.

JASON LENGSTORF: You know what? We're just going to move right into this, right next to it.

ALEX GARNETT: Uhhuh. I love how down to the wire this is. We have, like, seven minutes left. You successfully wrote a Temporal workflow, just basically on the spot here. [Laughter].

JASON LENGSTORF: How dare you. What are you even mad about, bro? It's right here. Look, there it is. You jerk. Broooo. Temporalworker.ts. I swear to god, this thing is killing me. Cannot find source lib Temporal activities imported from� do I need it to be a�.ts? Is that the problem?

ALEX GARNETT: Not when I do it. That's going to be environmentspecific and I'm not sure exactly why.

JASON LENGSTORF: "Acquire."

ALEX GARNETT: You're getting weird transpiling stuff.

JASON LENGSTORF: I don't know why this is happening. Can I just import this from Node Path...

ALEX GARNETT: We did everything right. We're just at the part of the exercise where we're picking and mixing TypeScript versus, like, Vanilla and that can be really annoying to do, depending on the environment you're already building in.

JASON LENGSTORF: How dare you.

ALEX GARNETT: The imports are not working due to transpiling we're doing.

JASON LENGSTORF: Yeah, we've got something goofy going on here. Create worker.

ALEX GARNETT: The funniest part is we're probably at a point you could probably ship this.

JASON LENGSTORF: I know. We don't have time to ship it. I really want to see this work. Come on, bro.

ALEX GARNETT: I'm very happy with the work we got done here and transpiling weirdness aside, you basically have a working Temporal app here, that you can now take from here.

JASON LENGSTORF: Yeah. Um, okay, so, no such file or directory. Is it because� it's probably because of this�.ts thing?

ALEX GARNETT: I think it's all goofy transpiling stuff.

JASON LENGSTORF: Please just work so we can see this goddamn thing function. Um...no such file directory. Boxhudworkflows.ts. It's resolving from the weird place. "Source lib Temporal" and now that path should work, based on where it's trying to be from. Aahh. Here we go.

ALEX GARNETT: You can refresh this. It successfully ran your first activity. You see some input persisted back in the service. Yes.

JASON LENGSTORF: There's the result. Note, we didn't log that. That got picked up�

ALEX GARNETT: That's automatically persisted. The other route, which should send that signal.

JASON LENGSTORF: I validate that does the thing and then I come back here and we have...

ALEX GARNETT: It received the signal and it ran the second activity and our workflow is complete. [Laughter]. There you go. Beautiful. [Laughter].

JASON LENGSTORF: Literally down to the wire. Thrilled that that worked. [Laughter].

ALEX GARNETT: I am really pumped that we got that done exactly in time.

JASON LENGSTORF: Okay.

ALEX GARNETT: You thought I was talking too much. [Laughter].

JASON LENGSTORF: Just to recap what we ended up getting done, basically a "human in the loop" durable workflow. So we set up the workflow. There's a little bit of boilerplate going on with this to make this run. We set up a worker, which is sort of a thing in the background. This have the part we would deploy to Fly.io, Kubernetes. We got it to validate my stuff and it's calling these things and orchestrating how it works. Loading the stuff. Then making sure we wait for the person to say, do the thing. Once they say, "do the thing," it completes the next step and returns the value.
This is why this is so powerful, once you get this boilerplate set up, everything else is straightforward. We are saying, I want you to do this and wait for somebody to do this and I want to do this and for the activities, themselves, this is exactly what I would write in my own utilities here. That's why this is cool and that's why I think� I hope everybody stuck through to actually see this result for why it's worth setting up this thing because then we get this really good visibility into what the heck is going on inside of our functions. We can see what went in, we can see what went out. When I get into sending the currentlylogged in user, when I get into sending arguments, like task IDs and, you know, what event we're at or what prize somebody's supposed to get, I'll be able to go through and actually peek at each step in the process. Holy crap, Alex, this was fun. This was a lot. [Laughter]. So, for folks who want to go further, learn more, where should they go next?

ALEX GARNETT: Go to Temporal.io. Go to docs.temporal.io. Go to learn.temporal.io. We show stuff. You can see how people are using Temporal to build applications in the real world. You can see sample architectures and things people are already building. Jason, thanks so much. I'm so excited we got that Mission Impossiblestyle.

JASON LENGSTORF: Thank you also much for hanging out. Thank you, Alex, for hanging out today. Thank you to G2I and to Vanessa, at White Coat Captioning, for doing the captioning today.
Check the schedule. If you're going to be at RenderATL, come find me. That's all we have time for today. Thank you, all, so much. We will see you next time.

Supporters

Become a supporter to see yourself here on future episodes!