0:00 All right, so the moment we've been waiting for. 0:01 It's finally ready to actually create the order 0:05 workflow. So let's go ahead inside of orders folder, 0:08 I'll create the 0:09 new order fulfillment 0:10 .workflow. And in NestJS, in 0:14 order to create a new workflow, we use the at 0:18 workflow decorator from NestJS workflows. 0:22 Here, we specify the name of the workflow. 0:25 Now, this will make it identifiable inside of 0:27 the Postgres 0:29 journal. 0:31 And of course, later on, when we want to actually invoke the 0:34 workflow from 0:34 somewhere else in the app, this is how we will identify it. 0:39 So I'll call this order-fulfillment. 0:43 And then we have the options for the workflow, which allow us to tune it. 0:48 Now, there's a couple of key options here 0:50 that I want to take a look at. 0:52 The first is the timeout for the workflow. 0:55 And you can see here, this is how long the instance of the workflow 0:59 can run for from start to end. This includes any 1:03 sleeps and waits that are part of the workflow. 1:07 Now, what this means is that this is the maximum total 1:11 lifetime of an instance, and we know an instance is just an 1:14 invocation of the workflow, so essentially a order fulfillment 1:18 request. So in our case, what we're going to do is set a 1:21 timeout of thirty days. This is essentially where 1:25 we say, after this point, we want to consider the order 1:30 fulfillment failed. And this is between the time 1:32 when the user placed the order 1:34 inside of the workflow to the very end of the workflow when they 1:38 actually receive their delivery. 1:41 And that's what our workflow is going to represent. 1:43 This is a wholly different mental model that you need to sort of wrap your head 1:47 around, is that the workflow that we're going to define here 1:50 is the order 1:51 fulfillment. It is long-running. 1:53 It spans over the entire lifetime of our business 1:56 operation, and we're going to see how that actually works. 2:00 And so this timeout value, once we reach this timeout, is saying 2:05 that if we reach this amount of time, thirty days after the start of the 2:09 workflow, 2:10 and it still has not completed, which in our case means it hasn't been 2:14 delivered to 2:14 the user, we consider it failed. And what we do is we 2:18 then call all of the compensating actions for 2:22 every workflow step. Now remember, these compensating 2:26 actions are the 2:28 undo functionalities that we've implemented in all of our 2:31 services. 2:32 So for example, the payment service, we have the ability to refund 2:37 a charged payment. And same thing for 2:40 inventory. After we reserve an inventory item, 2:43 we also have the 2:44 ability to release it. And that's exactly-- we have that 2:48 functionality is because when we do enter a failure state for the 2:52 workflow, we want to be able to undo all of the 2:55 steps 2:55 inside of it and essentially make it fully atomic. 2:59 If the user did not get their delivery, then of course, we want to refund them 3:03 and revert the inventory. You'll see all of this as we build the 3:06 workflow 3:07 out and it become more clear, but we wanted to set a global timeout to 3:11 essentially say how long the workflow can run from start to end. 3:14 Now, obviously, this is going to depend on the business logic 3:18 of the workflow that you're implementing and how long you want to let it 3:22 actually run for before it enters that failed state and starts 3:26 undoing all of the actions. 3:28 So that's a key property. The other property we want to look at 3:31 is the 3:32 concurrency setting for the workflow. 3:35 As we can see here, concurrency is how many instances may 3:38 run at once across every worker. Now, 3:42 the key difference between this concurrency and the one we defined at 3:46 the root of the application for the workflow module is 3:50 that this concurrency at the root is how many instances this 3:54 process of the application runs at once. 3:58 Any workflow, any type. It's a resource limit per 4:02 application. So this spans across every workflow. 4:05 But now we want to define concurrency limit on the single workflow 4:09 itself. 4:11 Here, this concurrency limit is a business rule that tells us how 4:15 many of this order workflow can be running at 4:19 one time. Another key component is that this is going to be enforced 4:23 globally across all instances of our application that 4:27 register as 4:29 a workflow in the database. So this concurrency limit works 4:32 across multiple copies of our app, which is very 4:36 critical. Now, this gives us so much power because what 4:39 we can do now, 4:39 as you can see in the example here, is limit, for example, 4:44 how many instances of the workflow run at once for 4:48 a single user. And that's what we're going to do. 4:51 We can define a concurrency object and set the limit 4:55 to one, which configures how many instances may 4:58 run at once of 4:59 the workflow across every copy of the app. 5:02 But then we can specify the key property, and this is going 5:06 to be a function that gets passed in the parameters 5:11 of the workflow. In our case, when we call this workflow, we're going to 5:15 call it with an order entity. And so that's what we're going to get 5:18 passed in 5:19 here, order type. And now what we can do 5:22 is return back on the order, the user ID. 5:26 Now, let's explain what this is doing. 5:29 This key here that we've now defined is essentially 5:33 partitioning the concurrency limit that we defined. 5:36 Instead of one order fulfillment globally, we're saying it's 5:40 one order fulfillment per user ID. 5:44 So customer A and customer B can each have an instance 5:49 running simultaneously of this workflow. 5:52 But customer A can't have two running at the same time. 5:56 This is super powerful functionality because now we can have 5:59 concurrency of this workflow across all users in our application 6:03 and still scale and have good throughput, but we can also limit 6:07 this to only run once 6:10 For a given user, and this will be key because this will 6:13 prevent, for 6:14 example, the same user from double charging their 6:17 card. 6:18 So now we have the workflow decorator configured. 6:21 We'll export class 6:23 OrderFulfillmentWorkflow. This is 6:27 gonna implement WorkflowRunner from 6:30 NestJS workflow that takes in two parameters. 6:34 One is the input into the workflow and then the response from the 6:38 workflow at its end. So here we're gonna, again, as I've 6:42 specified, pass in the order object as input to 6:46 the workflow, 6:47 and then for the return type, we're gonna create a new type called 6:51 FulfillmentResult. And then for the response, we have the 6:55 FulfillmentResult interface we've already defined here. 6:58 So that will include the charge ID to map back the 7:02 user's payment, and then the tracking number, which 7:05 is after 7:06 we shipped the user's order, how they can track it. 7:10 So critically, these are both pieces of information the client 7:13 might 7:13 need after they execute the workflow. 7:16 So now the order fulfillment workflow is fully set up and ready to 7:20 go. This is excellent. Now we're gonna set up our 7:23 constructor to inject the dependencies we need. 7:26 And then as we can see, this workflow runner, it expects us to 7:30 implement this one method called run. 7:33 This is called by a workflow request, which we're gonna be 7:37 executing later on. This run method is what actually starts the 7:41 workflow and resumes it from any given step. 7:45 And we expect to return the fulfillment result. 7:48 All right, so the first thing we'll do is add our 7:51 constructor and inject all three of the services that we've created 7:55 up to this point that we know are gonna be part of our workflow 7:58 and actually 7:59 implement the functionality of the workflow itself. 8:03 So that will include the payment service, the inventory 8:07 service, and then the mail service. 8:09 So go ahead and inject all three of these as private member variables. 8:13 And now we're finally ready to start implementing run itself. 8:17 Now you can see here in run, we actually got passed in two parameters. 8:21 The second here is the input that we will provide when we invoke the 8:25 workflow. That will be the order itself we're trying 8:28 to create. 8:29 Then we also have this context, the workflow context, and this 8:33 allows us to actually access the API for the workflow 8:36 itself and actually proceed the workflow through what are 8:40 called steps. So steps are what make up the workflow. 8:44 They are the logical pieces of business logic 8:48 that the workflow contains, but it also contains the 8:52 compensating action where we can undo each step.