AndrewDryga
Sage - Sagas implementation in pure elixir
Hi guys,
I want to show library I’m working on: Sage. It’s a dependency-free implementation of Sagas pattern in pure Elixir. It’s intended to simplify dealing with distributed transactions, especially with an error recovery/cleanup. Sage guarantees that either all the transactions in a saga are successfully completed or compensating transactions are run to amend a partial execution.
Elevator pitch is: “Sage is an Ecto.Multi for your business logic”.
With Sage you can do something like this:
import Sage
new()
|> run(:user, &create_user/2, &delete_user/3)
|> run(:plans, &fetch_subscription_plans/2)
|> run(:subscription, &create_subscription/2, &delete_subscription/3)
|> run_async(:delivery, &schedule_delivery/2, &delete_delivery_from_schedule/3)
|> run_async(:receipt, &send_email_receipt/2, &send_excuse_for_email_receipt/3)
|> run(:update_user, &set_plan_for_a_user/2, &rollback_plan_for_a_user/3)
|> finally(&acknowledge_job/2)
There are interesting papers I’ve used to get ideas for building it:
Most Liked
AndrewDryga
Sure! So for example we created a Stripe subscription while we were creating a user in our local database. This would be very simple example of a distributed transaction, where we synchronize state between local DB and remote DB and want state to be consistent after we finished the execution.
There can be three outcomes of a remote call: success, failure and unknown (we did not receive the response). When everything goes fine it’s still a simple HTTP call wrapped in DB transaction, but when something fails we need to make sure that we deleted effects on Stripe (deleted subscription) before rolling back the transaction.
Because Sage requires you to split transaction and compensation, and compensation match by transactions return - compensation by itself is easier to test (usually, smaller function you have - less complexity in the tests).
It’s worth to mention that Stripe for us is atomic database, internally they should clean up their effects when something fails. So if they did not close file socket - it’s their pain, we only need to amend effects we created.
I value mental model and semantics in Sage much more than underlying implementation.
You can read more in my blog post:
https://medium.com/nebo-15/introducing-sage-a-sagas-pattern-implementation-in-elixir-3ad499f236f6
Eiji
@AndrewDryga: You have a typo in your example.
Add & character before delete_subscription/3 at line: 6.

OvermindDL1
Touched this recently, it’s pretty cool. 







