1

After a record is updated, i need to perform a callout to an external system, in order to synchronize the data. For that purpose, i have created a record-triggered flow for my custom object that:

  • After record is updated
  • invokes an Apex invocable method (asynchronously) to perform the callout.

I will describe what i am trying to achieve with an example:

  1. User goes to the record, changes data and clicks the save button
  2. Record Triggered flow is triggered to make the callout (We don't know when the callout will be performed)
  3. User goes again immediately and updates again the record
  4. Record Triggered flow is triggered to make the callout (We don't know when the callout will be performed)

What i want to achieve is avoiding making the second callout if the first hasn't been performed yet.My goal is to avoid invoking multiple times the external system for the "same" dataset. Is there a way to handle this somehow? Could Platform events help handle this somehow?

4 Answers 4

4
+50

I would handle this with platform cache. Your invocable Apex should do the following high-level steps:

  1. define a cachebuilder class. See (https://github.com/trailheadapps/apex-recipes/blob/main/force-app/main/default/classes/Platform%20Cache%20Recipes/PlatformCacheBuilderRecipes.cls) for details. This cachebuilder class should store 'status' (enum) and 'result' properties in addition to the unique key
  2. Immediately on entrance to the invocable method, look up or create a cachebuilder instance.
  3. if the returned cachebuilder instance has status = status.INPROGRESS, return doing nothing further.
  4. if the returned cachebuilder instance has status = status.COMPLETE, return the cached result property. (remember to set your cache timeout)
  5. if the returned cachebuilder instance has status = status.NEW, invoke the callout and update the cachebuilder instance.

Some benefits of this approach:

  1. you're not doing DML on records, so you're avoiding the potential for recursive triggers/record triggered flow situations.
  2. this allows you to also cache the resulting data from your third party service for a period of time. This may be a two-edged sword, depending on what the third party service is doing.
  3. every org gets a bit of platform cache, so this is effectively using the platform to your advantage.

All of this hinges on a good understanding of the platform cache and cachebuilder class interface. The Apex-recipes link above is a great example of how to use cachebuilder but be sure to also look at it's corresponding test class. (https://github.com/trailheadapps/apex-recipes/blob/main/force-app/tests/Platform%20Cache%20Recipes/PlatformCacheBuilderRecipes_Tests.cls)

2
  • Hi Kevin, and thank you so much for the detailed analysis. Seems really interesting. Let me ask a question, how could i store the 'status' and 'result' values since the doLoad function gets only one input (String) ? Also, to ensure that i have understood completely what you suggest, should i "update" the 'status' value in the cacheBuilder after the callout end to "COMPLETE" ? Nov 2, 2021 at 20:32
  • @GiorgosGalaios - Good questions. Here's a better example of the round-trip nature of the cachebuilder. github.com/trailheadapps/apex-recipes/blob/… Note that the doLoad method in this cache builder class calls another method. In your case this other method would return an inner class with the properties. Happy to jump on a live share and go over it with you.
    – Kevin P
    Nov 2, 2021 at 21:40
6

You can consider a "Status" custom field to keep track of the callout status:

  1. When callout is initiated, update Status to "Initiated"
  2. When the callout finishes, reset the custom field value to blank (or whatever appropriate)

The flow should have additional logic to check if status is Initiated, then exit immediately, otherwise continue with the current flow.

2
  • Hi Hengky and thank you for your response. What do you mean "When a callout is initiated"? Updating a custom field before making the callout won't get me into troubles with the error "You have uncommited work pending" ? Since it seems to me that it will be like DML --> Callout --> DML within the same transaction Oct 29, 2021 at 20:48
  • 1
    I think i understood. You mean to do the following? Before record is saved, if status is not set to initiated, set it to "Initiated". After record is saved, the APEX Action will be invoked. Before calling the APEX Action i could add a condition checking the status. If it is not in "initiated" status, i could "skip" calling the APEX action. Am i right? Oct 29, 2021 at 20:59
2

The apex action invoked by the flow will be triggered synchronously, so at that point you can control for when it chooses to run the asynchronous method.

In the invocable method you can log each invocation (let's say to a custom object) and don't proceed if there was a previous invocation for this record in the last x amount of minutes.

(As an aside, regular HTTP callout are run synchronously, so unless it's first calling async apex or using Continuations, the entire process should run synchronously in any event).

2

Steps:

  1. Create a custom field "Is Awaiting Response" (IsAwaitingResponse__c) (or whatever name you prefer).
  2. Create a validation rule - Formula=IsAwaitingResponse__c, ErrorMessage=Please wait few minutes and try again, this record is waiting to be processed by remote system (or whatever text you want to show), ErrorLocation=Top of Page.
  3. Update record triggered flow: Add condition - If IsAwaitingResponse = false, then perform action - update record.IsAwaitingResponse = true, then perform action - invoke action (your current async invocable method). Else do nothing (exit the flow or perform any further action required).

Now, you are good to go. If anyone tries to update the same record which is under process, it will throw an error as per the validation rule configured in step 2.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .