Why do we add compensation action before action we want to compensate?

try {
  saga.addCompensation(activities::revertInventory, order);
  String inventoryResult = activities.updateInventory(order);
  logger.info(inventoryResult);

  saga.addCompensation(activities::refundCustomer, creditCardInfo);
  creditCardConfirmation = activities.processCreditCard(creditCardInfo, bill);
} catch (ActivityFailure e) {
  saga.compensate();
  logger.error(e.getMessage());
  throw e;
}

If activities.processCreditCard(creditCardInfo, bill); fails it means that nothing was processed, so do we really need to compensate? It looks like a customer will receive an extra refund

I think we need to add compensation after successful action isn’t it?

creditCardConfirmation = activities.processCreditCard(creditCardInfo, bill);
saga.addCompensation(activities::refundCustomer, creditCardInfo);

and if something after failed or cancellation requested only then we need to compensate

It’s strange to compensate a failed action

edu-errstrat-java

Hello,

So at this point in the code we are just registering the compensating action with the Saga object, not actually performing the compensation. This saga object will be used in the event a failure occurs.

We register it before the Activity executes because it would be impossible to add it after the activity executes in the current format. If we added it after the Activity call and that call failed, all lines of code after become unreachable, so the compensating action would never be registered.

If you don’t require compensation after a failed Activity, then you have the option to not implement that. This example shows performing a compensation on an Activity we know we want to compensate.

Yeah, I understand it

I think, it’s not the best example for educational purposes

saga.addCompensation(activities::refundCustomer, creditCardInfo);
creditCardConfirmation = activities.processCreditCard(creditCardInfo, bill);

If a processCreditCard method fails, a credit card balance is not changed, but a customer will receive an unexpected refund. It’s not good, or may be I missed something

Scenario with swapped lines:

if processCrediCard completed without exceptions, temporal will register a compensation because of a durable execution and it is ok (e.g. if cancellation request after withdrawal, it will be compensated) , and will not register a compensation for a failed action, because it did not change a state of credit card. And it makes sense: in a real world we would have something like a transaction Id that we are required to pass to a refund method

var transactionId = activities.processCreditCard(creditCardInfo, bill);
saga.addCompensation(activities::refundCustomer, new RefundContext(creditCardInfo, transactionId));

Yeah, I wanted to see that, thank you

saga.addCompensation(activities::refundCustomer, creditCardInfo);
creditCardConfirmation = activities.processCreditCard(creditCardInfo, bill);

The compensation implementation should be skipped if processCreditCard didn’t happen. This solves the scenario of processCreditCard succeeding, but returning failure, for example, because of a timeout.

If you can wait indefinitely and guarantee that processCreditCard fails only due to business errors and when the processing didn’t happen, then you can reverse the order of these statements.

But in situations when you can wait a limited amount of time and you need to deal with processCreditCard timing out then you have to register compensation before.