A few years back, I had a discussion about the correct way to build a retry-capable IS Trigger Service:
Recently, I was building a retry-capable IS trigger service where the steps involved a transaction, and I found some trickiness to it - so I thought I’d put what I learned out here.
A generic outline of the ‘Retry-capable trigger service – with transactions’ service is below.
Try/Catch - Outer Sequence Try Sequence <Work outside of transaction boundary> call startTransaction (e.g., insert multiple records into database) <Work within the transaction boundary> call commitTransaction Catch Sequence call getLastError If pipeline indicates transaction started ... ...(e.g. BRANCH on /lastError/pipeline/startTransactionOutput == /.+/) call rollbackTransaction If Exception is recoverable (e.g., database down) Set 'error' flag to ‘true’ If Exception is irrecoverable (e.g., database constraint violated) EXIT with failure (logs pipeline in PRT) BRANCH on 'error' flag If 'true', throw pub.flow:throwExceptionForRetry
The service does its work in a transaction in the ‘Try’ block. If an exception is thrown, control passes to the ‘Catch’ block where a BRANCH (immediately after ‘getLastError’) calls ‘rollbackTransaction’ to safely roll back the transaction without interfering with the retry mechanism.
The ‘rollbackTransaction’ call must be placed here. This is because it not only rolls back the transaction, but also seems to roll back the state of the pipeline to before ‘startTransaction’ (in the ‘Try’ block).
If rollback is invoked further down the ‘Catch’ block (e.g. after setting the ‘error’ variable),
it destroys pipeline variables created after ‘startTransaction’ (e.g. the ‘error’ variable). This
consequently breaks the service retry process (i.e. ‘throwExceptionForRetry’ does not get called.)