- Get Started
- Product
- Resources
- Tools & SDKs
- Framework
- Reference
- Get Started
- Product
- Resources
- Tools & SDKs
- Framework
- Reference
4.8.4. Compensation Function
In this chapter, you'll learn what a compensation function is and how to add it to a step.
What is a Compensation Function#
A compensation function rolls back or undoes changes made by a step when an error occurs in the workflow.
For example, if a step creates a record, the compensation function deletes the record when an error occurs later in the workflow.
By using compensation functions, you provide a mechanism that guarantees data consistency in your application and across systems.
How to add a Compensation Function?#
A compensation function is passed as a second parameter to the createStep
function.
For example, create the file src/workflows/hello-world.ts
with the following content:
4} from "@medusajs/framework/workflows-sdk"5 6const step1 = createStep(7 "step-1",8 async () => {9 const message = `Hello from step one!`10 11 console.log(message)12 13 return new StepResponse(message)14 },15 async () => {16 console.log("Oops! Rolling back my changes...")17 }18)
Each step can have a compensation function. The compensation function only runs if an error occurs throughout the workflow.
Test the Compensation Function#
Create a step in the same src/workflows/hello-world.ts
file that throws an error:
Then, create a workflow that uses the steps:
Finally, execute the workflow from an API route:
Run the Medusa application and send a GET
request to /workflow
:
In the console, you'll see:
Hello from step one!
logged in the terminal, indicating that the first step ran successfully.Oops! Rolling back my changes...
logged in the terminal, indicating that the second step failed and the compensation function of the first step ran consequently.
Pass Input to Compensation Function#
If a step creates a record, the compensation function must receive the ID of the record to remove it.
To pass input to the compensation function, pass a second parameter in the StepResponse
returned by the step.
For example:
1import { 2 createStep,3 StepResponse,4} from "@medusajs/framework/workflows-sdk"5 6const step1 = createStep(7 "step-1",8 async () => {9 return new StepResponse(10 `Hello from step one!`, 11 { message: "Oops! Rolling back my changes..." }12 )13 },14 async ({ message }) => {15 console.log(message)16 }17)
In this example, the step passes an object as a second parameter to StepResponse
.
The compensation function receives the object and uses its message
property to log a message.
Resolve Resources from the Medusa Container#
The compensation function receives an object second parameter. The object has a container
property that you use to resolve resources from the Medusa container.
For example:
1import { 2 createStep,3 StepResponse,4} from "@medusajs/framework/workflows-sdk"5import { ContainerRegistrationKeys } from "@medusajs/framework/utils"6 7const step1 = createStep(8 "step-1",9 async () => {10 return new StepResponse(11 `Hello from step one!`, 12 { message: "Oops! Rolling back my changes..." }13 )14 },15 async ({ message }, { container }) => {16 const logger = container.resolve(17 ContainerRegistrationKeys.LOGGER18 )19 20 logger.info(message)21 }22)
In this example, you use the container
property in the second object parameter of the compensation function to resolve the logger.
You then use the logger to log a message.
Handle Errors in Loops#
Consider you have a module that integrates a third-party ERP system, and you're creating a workflow that deletes items in that ERP. You may have the following step:
1// other imports...2import { promiseAll } from "@medusajs/framework/utils"3 4type StepInput = {5 ids: string[]6}7 8const step1 = createStep(9 "step-1",10 async ({ ids }: StepInput, { container }) => {11 const erpModuleService = container.resolve(12 ERP_MODULE13 )14 const prevData: unknown[] = []15 16 await promiseAll(17 ids.map(async (id) => {18 const data = await erpModuleService.retrieve(id)19 20 await erpModuleService.delete(id)21 22 prevData.push(id)23 })24 )25 26 return new StepResponse(ids, prevData)27 },28)
In the step, you loop over the IDs to retrieve the item's data, store them in a prevData
variable, then delete them using the ERP Module's service. You then pass the prevData
variable to the compensation function.
However, if an error occurs in the loop, the prevData
variable won't be passed to the compensation function as the execution never reached the return statement.
To handle errors in the loop so that the compensation function receives the last version of prevData
before the error occurred, you wrap the loop in a try-catch block. Then, in the catch block, you invoke and return the StepResponse.permanentFailure
function:
The StepResponse.permanentFailure
fails the step and its workflow, triggering current and previous steps' compensation functions. The permanentFailure
function accepts as a first parameter the error message, which is saved in the workflow's error details, and as a second parameter the data to pass to the compensation function.
So, if an error occurs during the loop, the compensation function will still receive the prevData
variable to undo the changes made before the step failed.