Saga Pattern Flashcards
(12 cards)
Compensation
Under the Saga pattern, reversion behavior for a particular step in a business
transaction is called a compensation.Compensations are an essential, required component of the Saga pattern. They
need to be idempotent, which means they can be executed multiple times
without causing side effects to the system or to the steps in the transaction. No
matter how many times you execute a compensation, it should achieve the same
results. Idempotency is important because it’s possible that a compensation may
need to be retried many times, should it fail
Saga Pattern
A design pattern for complex, multi-step business transactions that ensures
application consistency by using compensations to revert the system to its last
known good state when faced with failure at any point in the transaction.
Saga Execution Coordinator
SEC tracks the transient progress state from the results of the transaction steps
It determines the next valid step in the business transaction based on the
progress in the state of the business transactions.
It executes compensation steps in response to failure conditions within the business transaction.
Key to the SEC implementation is that it has knowledge of failure, and is programmed with the
ability to address the given failure with a compensation.
SEC and Temporal
A Temporal Workflow is the equivalent of a SEC. The Temporal Server does the work of scheduling the steps in the Saga for
execution. It manages retry behavior for each step. It also keeps track of the
history and progress of Workflows, which are displayed in a UI.
Developer Role in Saga with Temporal
2 things developer has to do.
The first is programming the Saga business rules as a Workflow along with its associated
Temporal activities. The second is writing the compensation that goes with each
step in the Workflow. Temporal does everything else.
Pseudo Code for Temporal Saga
// Import Temporal SDK constructs (conceptually)
import temporal.workflow.*
import temporal.activity.*
import temporal.saga.*
workflow OrderWorkflow {
// Define Activities activity PaymentActivity activity InventoryActivity activity ShippingActivity function placeOrder(orderDetails) { saga = new Saga(options: {compensationParallel: false}) try { // Step 1: Reserve Inventory inventoryId = InventoryActivity.reserveItems(orderDetails.items) saga.addCompensation(() => InventoryActivity.releaseItems(inventoryId)) // Step 2: Charge Payment paymentId = PaymentActivity.charge(orderDetails.paymentDetails) saga.addCompensation(() => PaymentActivity.refund(paymentId)) // Step 3: Initiate Shipping shipmentId = ShippingActivity.initiate(orderDetails.shippingAddress) saga.addCompensation(() => ShippingActivity.cancelShipment(shipmentId)) return {status: "SUCCESS", shipmentId: shipmentId} } catch (Exception e) { // Roll back previous steps using compensating transactions saga.compensate() return {status: "FAILED", reason: e.message} } } }
Saga Easy Defn
A saga is composed of two parts:
Defined behavior for “going backwards” if you need to “undo” something (i.e., compensations)
Behavior for striving towards forward progress (i.e., saving state to know where to recover from in the face of failure)
Temporal for Saga
Temporal, by design, automatically keeps track of the progress of your program and can pick up where it left off in the face of catastrophic failure.
Temporal will retry Activities on failure, without you needing to add any code beyond specifying a Retry Policy
Saga Pseudo Code
try:
registerCompensationInCaseOfFailure(cancelHotel)
bookHotel
registerCompensationInCaseOfFailure(cancelFlight)
bookFlight
registerCompensationInCaseOfFailure(cancelExcursion)
bookExcursion
catch:
run all compensation activities
Indempotency
sagas consist of two parts, the first part being those compensations. second part involves potentially retrying an activity in the face of failure
Temporal does all the heavy lifting of retrying and keeping track of your overall progress, however because code can be retried, you, the programmer, need to make sure each Temporal Activity is idempotent. This means the observed result of bookFlight is the same, whether it is called one time or many times.
Enabling Indempotency in Activity
You can use a distinct identifier, called an idempotency key, or sometimes called a referenceId or something similar to uniquely identify a particular transaction and ensure the hotel booking transaction occurs effectively once
The way this idempotency key may be defined based on your application needs. In the trip planning application, clientId, a field in BookingInfo is used to uniquely identify transactions.
type BookingInfo struct {
Name string
ClientId string
Address string
CcInfo CreditCardInfo
Start date.Date
End date.Date
}
You also probably saw the clientId used to register the compensation in the above Java workflow code:
saga.addCompensation(activities::cancelHotel, info.getClientId());