Pages

Sunday, 24 October 2021

London Salesforce Developers - Back in Person



20th October 2021 was a momentous day for the London Salesforce Developers Trailblazer group, as we met in person for the first time since 12th February 2020 - 88 weeks later!

We've still been running our events - like most of the developers around the world we had switched to Zoom, but fatigue was hitting, and the excitement of having people join from around the world was fading fast. After the September Dreamforce viewing party event, we organisers felt that enough was enough and it was time to go out into the world and once again mingle with the three-dimensional people.

Cloud Orca were our generous hosts, in the Loading Bay event area of their Techspace offices in Shoreditch: 

Your organisers and Cloud Orca CEO Ed Rowland (right)

It was wonderful to see everyone, and very strange to be in such proximity again. Dare I say it felt like we were returning to normal, although it will take a few more of these before it starts to feel normal again. Luckily we humans are nothing if not adaptable, so I'm sure by early next year we'll have forgotten what a virtual event feels like.

One thing we were fairly sure of what that people wanted to talk to each other rather than listen to us, so we kept the presentation side of the evening short and sweet.  Amnon kicked us off minimum slides to welcome everyone back, and call out some key community news, including :

then a few minutes from yours truly on the key developer features from the Winter 22 release

As part of my talk I gave a demo of dynamic interactions - while this wasn't recorded, you can find the code in my Winter 22 Github repository. I may write up another blog post about this, although there wasn't an awful lot more ground covered than my last post on the topic, aside from a slightly more plausible use case! It did feel good to be demoing to a live audience again - demoing virtually is fine, but you do feel somewhat removed from the audience.

Not quite Rick Wakeman - photo posted by Louise Lockie on Twitter

The RSVPs were a little lower than before the pandemic, which isn't surprising as not everyone is keen to start mixing again. Dropouts were way down though, so thus far it looks like those who sign up to come along aren't doing so lightly. 

If you are interested in coming along to our next meetup, make sure to join our Trailblazer Community Group, and if you'd like to see what we've got up to in the past and receive future recordings, follow our Youtube channel.  


 

Saturday, 2 October 2021

Transaction Boundaries in Salesforce (Apex and Flow)

Introduction

Winter 22 introduces the capability to roll back pending record changes when a flow element fails at run time. I had two reactions to this - first, that's good; second, how come it didn't always do that? It took me back to the (very few) days when I did Visual Basic work and found out that boolean expressions didn't short circuit - it's something that has been such an integral part of other technologies that I've worked with, it never occurred to me that this would be different.

The original idea for this post was to call out the roll back only applies to the current transaction, and tie in how Apex works as a comparison. But then it occurred to me that in the Salesforce ecosystem there are many people teaching themselves Apex who could probably use a bit more information about how transactions work, So, in the words of Henry James wrote in his obituary for George du Maurier (referring to the novel Trilby):

"the whole phenomenon grew and grew till it became, at any rate for this particular victim, a fountain of gloom and a portent of woe"

Transactions

A transaction is a collection of actions that are applied as a single unit. Transactions ensure the integrity of the database in the face of exceptions, errors, crashes and power failures. The characteristics of a transaction are known by the acronym ACID:

  • Atomic - the work succeeds entirely or fails entirely. Regardless of what happens in terms of failure, the database cannot be partially updated.
  • Consistent - when a transaction has completed, the database is in a valid state in terms of all rules and constraints. This doesn't guarantee the data is correct, just that it is legal from the database perspective.
  • Isolated - the changes made during a transaction are not visible to other users/requests until the transaction completes. 
  • Durable - once a transaction completes, the changes made are permanent. 

Transactions in Apex


Apex is different to a number of languages that I've used in the past, as it is tightly coupled with the database. For this reason you don't have to worry about transaction management unless you want to take control. When a user makes a request that invokes your code, for example a Lightning Web Component calling an @AuraEnabled method, your code is already in a transaction context. 

When a request completes without error, the transaction is automatically committed and all changes made during the request are applied permanently to the database.  This also causes work such as sending emails and publishing certain types of platform events to take place. Sending an email about work that didn't actually happen doesn't make a lot of sense, although this caused us plenty of angst in the past as we tried to find ways to log to an external system that a transaction had failed (Publish Immediately platform events finally gave us a mechanism to achieve this).

When a request encounters an error, the transaction is automatically rolled back and all changes made during the request are discarded. Often the user receives an ugly stack trace, which is where you might decide that you need a bit more control over the transaction.

Catching Exceptions


By catching an exception, you can interrogate the exception object to find out what actually happened and formulate a nice error message to send to the user. However, unless you surface this message to the user, you have changed the behaviour of the transaction, maybe without realising it. For example, if you catch an exception and return a message encapsulating what happened:
Account acc=new Account(Name='TX Test');
Contact cont=new Contact(FirstName='Keir', LastName='Bowden');
String result='SUCCESS';
try {
   insert acc;
   cont.AccountId=acc.Id;
   insert cont;
}
catch (Exception e) {
    result='Error ' + e.getMessage();
}

return result;
In my dev org, I have a validation rule set up that one of email or phone must be defined, so the insert of the contact fails and I see a fairly nice error message:


Unfortunately, this isn't the only issue with my code - as I caught the exception, the request didn't fail so the transaction wasn't automatically rolled back. While from the user's perspective the request failed, the first part of it succeeded and an account named TX Test was created:


and every time the user retries the request, I'll get another TX Test account and they will get another error message.  

Taking Back Control


Luckily Apex has my back on this, and if I want to (and I know what I'm doing) I can take control of the transaction and apply some nuance to it, rather than everything succeeding or failing.

Savepoints allow you to create what can be thought of as sub or nested transactions. A savepoint identifies a point within a transaction that can be rolled back to, undoing all work after that point but retaining all work prior to that point.  Changing my snippet from above to use a savepoint and rollback, I can ensure that all of my changes succeed or fail :
SavePoint preAccountSavePoint=Database.setSavePoint();
Account acc=new Account(Name='TX Test');
Contact cont=new Contact(FirstName='Keir', LastName='Bowden');
String result='SUCCESS';
try {
   insert acc;
   cont.AccountId=acc.Id;
   insert cont;
}
catch (Exception e) {
    result='Error ' + e.getMessage();
    Database.rollback(preAccountSavePoint);
}

return result;
The user's experience remains the same - they receive a friendly error message that the insert failed.  This time, though, there is no need to rid my database of the troublesome account. Prior to carrying out any DML I have created a Savepoint, and in my exception handler I rollback to that Savepoint, undoing all of the work in between - the insert of the account that was successful.  Note that rolling back the transaction has no effect on my local variables - prior to the return statement I have an account in memory that has an Id assigned from the database, but that doesn't exist any more. Of course this also leaves any other work that happened in the transaction outside of my code in place, which may or may not be what I want, so taking control of transactions shouldn't be done lightly.

Savepoints are also useful if there are a number of courses of action that are equally valid for your code to take. You set a Savepoint and try option 1, if that doesn't work you rollback and try option 2 and so on. This is pretty rare in my experience, as usually there is a specific requirement around a user action, but it does happen, like when option 2 is writing the details of how and why option 1 failed.
 
You can also set multiple Savepoints, each rolling back less and less work, and choose how far rollback to in the event of an error. Your co-workers probably won't thank you for this, and are more likely to see it as combining Circles of Hell into your very own Inferno.  If you do choose to go this route, note that when you rollback to a savepoint, any that you created since then are no longer valid, so you can't switch between different versions of the database at will.

Savepoints only work in the current transaction, so if you execute a batch job that requires 10 batches to process all of the records, rolling back batch 10 has no effect on batches 1-9, as those took place in their own transactions which completed successfully. 

Rolling Back Flow Transactions

After that lengthy diversion into Apex, hopefully you'll understand why I expect everything to rollback the transaction automatically when there is an error - it's been a long time since it has been my problem.  Per the Winter 22 release notes, flow doesn't do this :

Previously, when a transaction ended, its pending record changes were saved to the database even if a flow element failed in the transaction

and bear in mind that is still the case - what has been added is a new Roll Back Records element you can use in a fault path. Why wasn't this changed to automatically roll back on error? For the same reason that Visual Basic didn't start short circuiting boolean expressions - there's a ton of existing solutions out there and some of them will rely on flow working this way. While it's not ideal, nor is introducing a breaking change to existing customer automation. 

Something else to bear in mind is that this rolls back the current transaction, not necessarily all of the work that has taken place in the flow. Per the Flows in Transactions Salesforce Help article, a flow transaction ends when a Screen, Local Action or Pause element is executed. Continuing with my Account and Contact approach, if you've inserted the Account and then use a Screen element to ask the user for the Contact name, the Account is committed to the database and will persist regardless of what happens to your attempt to create a Contact.  Much like the batch Apex note above, rolling back the second (Contact) transaction has no effect on the first (Account) transaction as that has already completed successfully.

Enjoying Transactions?

Try distributed transactions:

And then scale them up across many microservices.

Many years ago I used to have to manage transactions myself, and in one particular case I had to work on distributing transactions across a number of disparate systems. It was interesting and challenging work, but I don't miss it!

Related Posts