I came through this Trailhead module to better understand Service Layers with some fflib examples.
Unfortunately I am struggling to understand this part:
The following example uses a SavePoint to encapsulate and wrap the database operations within a Service method. As per the design considerations, the SavePoint is used to avoid the caller catching exceptions (perhaps resulting from the second DML statement). That would then result in the Apex runtime committing updates to the opportunity lines (first DML statement), causing a partial update to the database.
Given the provided example in the applyDiscounts
method of the Trailhead module, I do not see the benefits of encapsulating the 2 DML updates in a try/catch and rollback if the exception is thrown.
As far as I understand, this would be the default behaviour as well and both DML will be rolled back if the second fails.
Since it is as well throwing the catched exception after the specific rollback of those 2 DMLs, the default rollback will continue on the caller.
I see how if it does not handle the rollback inside of the Service method, the caller can also Set a SavePoint before the Service method call, wrap the call in a try/catch, and rollback if any exception has been thrown in the Service method. This would basically result in the same outcome.
Is this specifically what it aims to avoid? So that logic is encapsulated in the Service method and we can keep the caller cleaner?
What if (unlikely) from the caller perspective, we would want the first DML to be actually commited even if the second fails? This approach makes it not possible, providing claningness against flexibility.
registerXX
methods were called with expected arguments and never have to actually execute the DML with all the attendant side effects. See Chapter 12 of Fawcett's book, 3rd edition