Writing idempotent tasks
For tasks that run repeatedly, for the same resource, it's often important to make sure they only perform their work once. Some common examples:
- A task that sends an email when a customer is tagged should only send that email once
- A task that cancels an order when tagged should only try to cancel that order once
Tasks in this category usually respond to Shopify's "update" events, in which events all of the same topic arrive multiple times for the same resource. (This isn't always the case; this kind of scenario can also come up with custom webhooks.)
To illustrate: because there is no "order has been tagged" event topic, a task must subscribe to shopify/orders/updated, which arrives whenever the order is updated, for any purpose; the task must then inspect the tags of the order every time, to see if the tag of interest is in place.
Taking the example of sending an email when an order is tagged, if a task simply sends an email for every shopify/orders/updated event so long as that tag is present, there's potential for many emails to be sent – one for every order update, in fact, so long as the order happens to have that tag at the time of order update.
Without intentional coding, a task that simply reads an event that may occur many times (e.g. any "update" event) may unintentionally perform the same actions many times.
Tasks can sidestep this issue by ensuring that they can "remember" that their work has been done. In this way, a task can become idempotent – no matter how many times a similar event appears, the task will only perform its action at most once.
Techniques for this:
- Add a tag indicating that the work has been performed. For tasks that send an email when a resource has been tagged, this would mean adding another tag to the resource at the time the email was sent, perhaps "email-sent" (or something more specific). The task must also check to see if this additional tag is already in place, for subsequent runs. If it's present, the task knows that it shouldn't send the email (or add the tag) again. (For an example, see the Email customers when tagged task, noting the email_sent_tag variable.)
- For tasks that modify the resource in question in some other way, simply check to see if the resource is already in that modified state. For tasks that auto-cancel orders, for example, the task should check to see if the order is already cancelled – if it is, no work should be performed.
- Use the cache to set a value indicating that the task's work has been performed. Update the task code to check that same cache key – if present, do not perform any work. (Note: cache values have a maximum lifetime of 60 days, making this technique unsuitable for some purposes.)