0:00 All right. So in the interest of time, I've created some new code here 0:04 that I wanna walk you through inside of orders. 0:07 We've added a new orders controller, a new 0:10 orders.schemas, and a new orders.service. 0:14 So this is all going to allow us to now interact with our orders and our order 0:18 workflow through an HTTP controller. 0:21 So we've added this new HTTP orders controller, 0:25 and we're injecting this new order service, 0:28 and we're adding some routes here. 0:30 So we have a route to place a new order, 0:33 find a single order with a route parameter. 0:37 Also, we're gonna get a fulfillment route, which will include some details 0:41 from the workflow instance that is currently running. 0:44 And finally, I'm gonna show you a cancel functionality. 0:47 So we have a post route here that allows us to cancel an existing 0:52 order, which is going to cancel the running 0:54 workflow instance if 0:56 it's already executed and is in that waiting state. 0:59 So we have these HTTP routes. We also have 1:02 orders.schemas where I've created a simple Zod 1:05 schema to outline the data we need to place a new 1:09 order. So that includes the user ID, the order 1:13 items, which is an array of objects with a 1:16 product ID, 1:18 quantity, and price, 1:21 a shipping details, and a payment method. 1:24 So we're just creating some types out of this. 1:27 And also cancel order takes in a reason. 1:30 So this is going to allow us to validate our controller. 1:34 And then finally, we have the order service where we're now 1:38 actually invoking the underlying workflow. 1:41 So let's walk through this into greater detail. 1:44 So the first method we have is the place method, 1:47 which takes in the place 1:48 order DTO. 1:50 We are doing some validation here with the schema that we 1:54 defined. 1:55 We're then creating an order entity that we know can actually go in the 1:58 database, and then we are actually entering a new database 2:02 transaction to insert the new order into the database. 2:07 So to do this, notice in our constructor, we've added our Drizzle 2:11 ORM database, and we've also added this new dependency 2:14 called workflow client. So this is workflow client 2:18 imported from NestJS Workflows. And this is how we actually 2:22 interact with a workflow outside of it using this injected 2:26 workflow client. 2:28 And you can see here, we've opened up this database transaction 2:31 where we're 2:32 now inserting the new order into the database. 2:35 However, in the very same transaction, we're starting the 2:38 workflow for our order fulfillment. 2:42 So only if the insert into the database succeeds, 2:46 then we're using the workflow client here and using the start 2:49 method. This will create a new instance of the 2:53 workflow where it takes in the name of the workflow. 2:56 So here we pass in the order fulfillment workflow instance 3:00 type directly, 3:02 and then we pass in the parameters that it expects. 3:05 So here we specify that's the order. 3:08 So that is exactly what we pass in, the newly created order that went 3:12 into the database. And then finally, the third 3:15 parameter here is an options object. 3:19 So here in this options, we're specifying a few things. 3:22 The first is the ID of the workflow 3:26 instance. 3:27 Now, by default, this is a random UUID, 3:31 but the benefit of using a fixed known ID here is 3:34 that if we try to start a workflow instance with the same 3:38 ID, it's going to return the existing instance 3:42 instead of 3:42 starting a second one. And so it tells us to derive it from a 3:46 business 3:46 key, and that's exactly what we do. 3:49 We have this fulfillment ID function that just returns 3:52 back order dash with the new order ID. 3:55 And this is really useful because it means if we ever tried to 3:59 create a workflow with the same order ID, well, it won't 4:03 recreate it. It will just deduplicate and return the existing one. 4:08 We also have to pass in our ORM's transaction here. 4:11 So that's the Drizzle ORM transaction. 4:14 And this ensures that 4:17 the instance, the workflow instance that we're creating is created in the 4:21 same transaction of actually inserting the order 4:25 into up above. So both of these have the same database 4:28 transaction, 4:29 and the workflow instance will be saved in the same one, so they both succeed or 4:33 fail together. 4:35 And the last thing I wanna show you, this property here is priority, which is 4:38 really cool. You can actually specify the priority of 4:42 the 4:42 workflow instance so that if you have multiple, it will actually 4:47 run lower numbers first. So what we're doing here 4:49 is checking on the 4:50 order. If the shipping is equal to express, then we 4:54 specify this workflow as one to make sure it runs before 4:58 standard ones. Otherwise, we run it as two. 5:02 So this is really nice way to actually tell NestJS 5:06 workflow what order it should run in. 5:08 So now you can see how to actually invoke a workflow from outside 5:13 using the workflow client. 5:15 We have a couple of other methods here. 5:17 Find one is just using Drizzle ORM to find an existing order. 5:22 And then we have fulfillment, which I wanna show you. 5:25 Here, we're actually using the workflow client, 5:28 and it has a method 5:29 called get status. So here, this will return us the workflow 5:33 instance and the workflow signals that it's waiting for, 5:37 its journal, and any children. So this simply takes in the ID 5:41 of the workflow instance, which since we know we've set this to a 5:45 fixed identifier, we're simply just passing in 5:48 that same identifier 5:49 function with the order ID to get access to it. 5:54 And then we pass through journal to true to make sure it returns us back the 5:57 entire journal for that workflow. 6:00 So this will allow us to actually see it in action. 6:04 And so here, this return object is a small view over the workflow 6:08 instance, which includes a lot of data 6:10 that we'll s-see in the 6:11 demo. 6:13 So it'll show us the status that we set in the workflow, 6:17 any waits that it's r-waiting on, how many times it's been run, 6:21 its current steps in the journal, and any errors. 6:25 So nice to see how we can get access to all this debugging information as well. 6:30 And then finally, I wanna show you cancel. 6:33 So this is the cancel route that we had. 6:37 And what we can actually do inside of here is, as you can see, we're retrieving the 6:40 order from the database, and then we're using the workflowClient 6:44 has a new cancel method that will actually request 6:48 cancellation of the instance workflow. 6:52 So we simply pass in the ID of the instance we wanna cancel, as 6:56 well as an optional reason that we pass in, and we can see here it'll 7:00 return us back this accepted Boolean if it was able to be 7:04 canceled. So if the workflow has already run to 7:07 completion, this will 7:08 be false. Otherwise, this will actually stop the workflow and 7:12 execute any compensating actions to undo it. 7:16 So this is really nice. We have this way to manually fail or 7:20 cancel our workflows and roll them back at any point in time. 7:24 Now, the next two methods here are going to be helper methods that 7:27 are gonna be 7:27 used by our carrier webhook. So when a shipment gets 7:31 marked as delivered or is returned from the carrier, we're 7:35 gonna call these methods in the order service that take in the carrier 7:39 event, and then we're gonna call this common 7:42 updateAndSignal method that takes in the type of event. 7:47 And so let's look to see what we're actually doing here inside of 7:49 updateAndSignal. 7:52 So in here, we're opening a new database transaction, 7:55 and the first thing that 7:56 we're gonna do is 7:58 update the order status to make sure it's at the correct 8:02 one in the database. 8:06 And then critically, we're using the workflowClient.signal 8:09 method. This is how we issue those signals to the 8:13 instance that is waiting using the 8:17 waitForAny or signalWait method. 8:20 So here it's waiting for any of these signals, and this is how we actually issue 8:24 them. We use the workflowClient.signal, 8:28 which takes in the actual signal s-string value, which if you 8:32 remember from our 8:34 order carrier event, is shipment delivered or shipment 8:38 returned. 8:39 And that's the same exact type that the carrier event 8:43 expects. So that's how we are 8:46 passing the same signal type through. 8:49 We then pass through the carrier event itself, which we know the signal 8:52 expects. And finally, we have an options object. 8:56 So again, here we're specifying the all-important 8:59 key. 9:00 That's gonna be the event.reference from the carrier event, which is our original 9:04 order ID. 9:05 Remember, this signal will only respond to the same 9:09 key that 9:09 it set-set up inside of the workflow. 9:12 So here we're saying, "Wake up this workflow with this key of 9:16 order 9:16 ID." 9:18 And then inside of the workflow itself, we are passing that same key in 9:22 here. So these two need to match for it to 9:24 actually wake up. 9:25 We're then passing in an ID. Again, allows us to deduplicate this if we 9:29 ever send it more than once. And the same database transaction 9:33 to ensure these two updates happen together. 9:37 So now we have all of the functionality the order service needs to interact with 9:41 the workflow from the outside. 9:43 The last thing I wanna show you is I've created this CarrierWebhook 9:46 controller 9:48 inside of the shipping directory, and this just exposes a 9:51 simple async handle method, a post route, where we take 9:55 in this carrierEvent schema that we defined with all of the data 9:59 that we need. 10:01 And then we're injecting that same order service 10:04 and simply checking 10:05 to see if the event type is shipment delivered. 10:07 We call the markDelivered with the event, or shipment returned, we 10:11 call markReturned. And we know this is going to issue that signal 10:15 to the workflow and wake it back up to get processed once again. 10:19 Now finally, in my root app module, I've added the order service to the 10:23 providers array and our two controllers to the controllers array 10:26 so that they are picked up by the NestJS application. 10:30 And at this point, we actually have everything we need to now 10:33 test out the 10:33 workflow, place a new order, and start triggering it 10:38 and see how everything actually works in action. 10:40 So let's go ahead and see how this works. All right. 10:43 So now to actually show you this workflow running inside of action, 10:48 I've created this new demo TS file at the root inside a script, 10:52 and I've also created some corresponding package JSON 10:56 commands which will run the demo script, as well as a few different 11:00 states that we're gonna run through. 11:02 And these are all described inside of the script. 11:05 So we have a happy path, a way to run all 11:09 scenarios, and then the scenarios themselves. 11:11 So we can actually run through a workflow where, for example, the card 11:15 gets declined, and it'll show that that payment fails, that we then 11:19 compensate everything. Same with out of stock, canceling 11:23 after payment, and carrier returns package in 11:27 case the carrier webhook does not succeed. 11:30 So to run this, make sure you have the server running, 11:33 and then start up a 11:34 new terminal in the same directory. 11:36 And so now I'm going to go ahead and run bun demo 11:40 command. This is gonna go ahead and firstly set up our 11:43 database to have all of the data that we need 11:47 pre-seeded. So seed some stock data. 11:50 And then you can see here we run through the happy path. 11:54 So the first thing we do is place a new order with the place order 11:57 HTTP route, and you can see the details here. 12:01 And then we can see we've actually started to pull- 12:05 Until workflow is awaiting delivery. 12:07 So we know place order starts the workflow, and at this point, 12:11 the workflow goes through all of its steps until it waits for 12:15 a signal. 12:17 And that's exactly what we do here. 12:19 We pull until it is awaiting delivery. 12:22 At this point, we know that the payment was charged 12:24 and the stock was reserved. 12:25 We checked the database, and this looks correct. 12:28 And now we simulate the carrier webhook for shipment.delivered. 12:32 Webhook was accepted, and now we should expect to check 12:35 fulfillment after delivery. And that means the workflow was then 12:39 resumed, where remember, it enters that waiting state. 12:44 And we can even see the steps the workflow has gone through here. 12:48 So charge payment, reserve stock, await delivery, 12:53 delivered, and now it is inside of the before review request 12:56 state 12:56 because of the custom status messages that we set up. 13:00 You can finally see the final order status here has been marked as 13:04 delivered, so this is working properly, and now it is waiting up to seven 13:08 days for the order reviewed email to go out. 13:11 So this is really cool to see. 13:14 Now, you can also see the different scenarios 13:16 that we have set up here. 13:17 For example, canceling. We place the order, wait until awaiting 13:21 delivery, and then cancel it. Cancel gets accepted, and 13:25 then the workflow ends. And we see the same thing for carrier 13:28 returned 13:29 here, where we walk through the workflow 13:31 and then send the 13:32 shipment.return signal now instead of delivered. 13:36 And that will also exit the workflow here. 13:38 So this is all looking excellent, just as we'd expect. 13:41 And we can see the power of these NestJS workflows, where they allow us 13:45 to essentially model our real-world business functionalities 13:48 and workflows that 13:49 can span over a period of time. We can pause the execution, 13:53 store it in the database, and resume it. 13:56 This also comes with the added benefit of being able to retry every single 14:00 step automatically, and we can even roll things back at any 14:03 point to keep everything fully atomic, which is an amazing benefit. 14:07 So I hope you learned so much in this lecture about NestJS workflows, how we 14:11 can build them into our applications, and their use cases. 14:15 Thanks so much for watching, and I'll see you in the next one.