Let's say I have an API method with can be used to calculate the sum of all orders made by a specific customer:
Amount CalculateOrderSum(int customerId)
{
// Perform authentication to make sure caller has access to customerId
// Retrieve customer with id customerId
// Retrieve all orders related to the customer
// Retrieve details for different orders (not always, depending on state)
}
At any point in this function, some system administrator may purge old items from the system. This means that one of the below can happen while the method above is running:
- The authentication will fail, because the customer no longer exists
- The customer can't be loaded, since it was deleted
- The customer can be loaded, but a millisecond later the orders are deleted and cannot be retrieved.
- Retrieving order details works for some orders, but fails for others since they have been deleted mid-processing.
I want the application to return a friendly error message when any of this happens, rather than returning a NullReferenceException or similar.
As I see it, there's some different approaches to add error-handling for this logic:
- I could introduce a lot of null-checks throughout the code for example: if (customer != null) throw OrderRetrievalFailedException("customer is a goner."). Since all the data in the database can be purged at any time, this would lead to quite a lot of if's spread throughout the code (which seems to get messy)
- I could change the purging functionality to mark customers or orders as deleted (rather than actually removing the database rows). This way the function could still do its work because the data will still be there. The issue here is that we actually want to purge old data for different reasons (less attack surface and performance considerations for example).
- I could change all methods to throw if an object can't be loaded (so GetOrders(customerId) could throw CustomerNotFoundException if the customer cannot be loaded) which would be catched in the CalculateOrderSum function and an error given to the user. So basically the code would have to be littered with if (something == null) throw new SomeException.
- I could introduce some global locking mechanism, so that a customer can't be deleted while any one is reading any of its data. The issue here is that our system is distributed so we would need to implement a central locking mechanism. Also, I have a bad experience with locking of database rows in use-cases like this in high-traffic database.
All of these approaches feels quite convoluted and tricky to get right to me, and it means that the "main success flow" of the code will be littered with handling of exception scenarios. I'm leaning towards alternative 3, but I would to hear if there's some other "standard and robust" way of handling this.
(I'm using C#, but I assume that the same issue would apply to users of for example Java or C++)