1

What is the best way about getting the final sObject state from an update? I realise I could create an event that would trigger after commit to get a very likely final state but I want to get the absolute final state so I can push this out to another service.

KarlLivesey
  • 579
  • 3
  • 18

2 Answers2

1

I would go with the Platform Event approach, as both you and @sfdcfox have mentioned.

However, bear in mind that a trigger can be invoked multiple times for the same DML action (and before/after phase) for the same record in the same DML flow (let alone in the over-all transaction) if you have any non-Apex automation (flows, process builders, workflows) that updates the record, as per step 14 in the Triggers and Order of Execution processing.

As such, I recommend you have a static set of IDs for records for which you have already created and published a Platform Event (you will need to only do this in the after phase to ensure an ID is available) and suppress creating and publishing any more for those IDs in the current transaction.

You should use an After Commit publish behaviour. That way the platform events will only be dealt with after successful completion of the over-all transaction against Salesforce (so when the SObject DML actions are finalized in the database).

Note that if this platform event handling is implemented using a trigger-based subscriber then the PE daily limits are not applied (these apply to CometD and EMP only as per the documentation). These executions are also not counted towards async executions either (though therefore have synchronous processing limits applied to them).

You'll then need to perform some form of callout to send the details to the external system.

Being based on a trigger, you cannot directly perform callouts and will have to actually handle that in async processing (e.g. via a future or queueable fired from the trigger-based subscriber) and this is then when you face your daily limits. The good news is that the platform events are processed in chunks (of up to 2000 at a time) and you can leverage this if you are able to construct composite callouts to your external system, sending the up to 2000 "final SObjects" in a single callout (callout payload maximum size and external system willing; see also the callout limits and limitations documentation).

Salesforce may even aggregate up platform events published in separate calls to EventBus.publish that happen in the same over-all transaction, for passing to your trigger-based subscriber (I've not verified this though).

Phil W
  • 36,007
  • 4
  • 45
  • 91
  • 2
    One caveat to keep in mind depending on what this solution is intended to provide, platform event messages (and change data capture events) are not guaranteed to be delivered. They may be published and silently never received by any subscriber (including apex subscribers). – Mark Pond Mar 17 '21 at 23:58
  • The main purpose of this is that we have an external tool we use for timesheets and expenses etc we want any updates to this data on salesforce to be pushed out to this external platform as they happen but we have hit the issue of using triggers only not giving us 100% consistent output based on OOE and multiples sent. – KarlLivesey Mar 18 '21 at 01:49
  • @markpond please reference where it is documented that apex subscribers are not guaranteed delivery. I have not heard this before. – Phil W Mar 18 '21 at 07:13
  • The closest I know of is in this doc which discusses persistence at the end; I suggest this is to cover the unlikely event of a server failure, and subsequent loss of queue content, rather than something likely to occur. So, yes, there is a very small chance events could be lost. – Phil W Mar 18 '21 at 07:46
  • But, as they say, the only guaranteed thing in life is death. :D – Phil W Mar 18 '21 at 07:47
  • @KarlLivesey would it be impractical to mark records as needing synchronization and only after successful sync mark them as no longer requiring it (directly or implicitly using FOR UPDATE in the sync processing to ensure you cannot have a race condition against the flag)? If so, consider what we call an adaptive batch as the means to implement this via a single-threaded, selectively executed and self-chaining batch. – Phil W Mar 18 '21 at 08:45
  • 1
    @PhilW it is much more than a small chance of failure to deliver, I've seen it first hand a significant amount. The statement at the end of the considerations docs for both PE and CDC is very recently added (v49?) as a direct result of utilizing those features in a solution that required guaranteed delivery and then seeing that not all messages were being delivered. (real world use case: don't use PE or CDC for building something like an audit logger for compliance, where every 'message' is critical) – Mark Pond Mar 18 '21 at 17:56
  • @MarkPond interesting you've seen it a significant amount. I guess our customers have been lucky? Regardless, any transaction can fail and may not be easily reconstructed (consider a queueable with some state that fails - yes, the finalizer will help in this case, I hope, when it GAs). We do belt and braces: not only do we fire a platform event, we also ensure the record is tagged as needing processing. We then have async processing from the PE that effectively only uses the PE as a kick up the rear to execute in a single-threaded manner, chaining automatically to address newly marked records. – Phil W Mar 18 '21 at 18:06
  • @PhilW It would be nice to do it that way but we run into the issue of possibly missing updates they want the entire object history to be tracked also and that would require me to spam query to hopefully capture all changes as they happen. – KarlLivesey Mar 18 '21 at 18:07
  • Well, some requirements are simply impossible to realize robustly when you work within the constraints of the Salesforce platform. Not saying this one is one of those, but it's looking increasingly like it (depends on the required through-put per hour or 24hr period as addressed by sfdcfox in his answer). Perhaps you need a "transaction history" representation of the object, maybe even linked to a given session via a session ID (this probably won't fly as you don't always have one of those) that are inserted for sending, then deleted once sent (via an async approach like I outlined)? – Phil W Mar 18 '21 at 18:12
  • so I suppose the most effective method would be a new custom object that contains a long text field contains a serialised copy of the object with changed fields only while still triggering an event to try and keep it as realtime as possible but then having a query that grabs all records on a more leasurly scale to grab missed data – KarlLivesey Mar 19 '21 at 17:03
  • I guess it depends on just how "real time" you want. Personally I'd go with the "adaptive batch" approach (which will only delay the synchronization by up to a couple of minutes in general). You may miss the fact that certain changes were done in a couple of separate updates, instead only sending the resulting aggregate set of changes, but you will have a robust way of processing the data in a governor limit sensitive manner. It does, as always, depend on your exact requirements and whether you have any wriggle room to adjust these based on technical constraints. – Phil W Mar 19 '21 at 18:12
0

You have many options available to you. You can use a @future method, a Queueable class, a Workflow Outbound Message, a Capture Data Change trigger, or an After Commit Platform Event, or third-party support such as an Enterprise Service Bus or replication service using the Updated sObject/getUpdated resources.

Which is "best" is a bit arbitrary, as it depends on what capabilities you have both in Salesforce and the third-party service, but these are all potential solutions depending on data volume. Basically, anything that can run after the final commit is a viable option, and all of these can (or by their very nature, do) run only on the final state of an sObject.

The future/queueable option has a limit of 250,000 transactions per day, outbound messaging should be 1,000/hour (24,000/day), CDC and Platform Events are 10k-50k per day depending on Salesforce Edition, and off-server solutions are only limited by your API rate limits, which are much higher for most orgs. You'll want to consider how much code you want/are willing to develop, how large the expected data volume is, etc.

sfdcfox
  • 489,769
  • 21
  • 458
  • 806
  • we need it to be as "real-time" as possible I don't mind the code but this is for timesheets expenses etc. it makes calling this from an external platform not really useable. – KarlLivesey Mar 18 '21 at 01:59