Nebo15 / sage

A dependency-free tool to run distributed transactions in Elixir, inspired by Sagas pattern.
MIT License
912 stars 40 forks source link

Pass latest effect to every transaction #45

Closed ajengs closed 4 years ago

ajengs commented 4 years ago

Hi, I'm just starting to use this library, which would really help to solve my dependencies hell. But my case will need latest effect to be processed by next stage transaction. I know that we can access effects_so_far, but we will need to know the previous stage name executed.

I'd like to have latest_effect so each stage does not need to care what is previously executed and can access the latest parameters directly.

Please help to see if it makes sense or is there any other better way to achieve this.

AndrewDryga commented 4 years ago

Hello @ajengs, sorry for the delayed reply.

Can you show me how do you build your Sagas? The idea is that functions indeed do not need to know name of previous step, instead you want to use those names when you build your saga, eg.:

...
|> run(:user, &create_user/2)
|> run(:plans, &fetch_subscription_plans(&1.user, &2), &subscription_plans_circuit_breaker/3)

This makes the variable that steps are using explicit and under your control. If you would just always use last effect (which is not always the case as sometimes you want multiple effects), then somebody can reorder the saga and break it without noticing.

ajengs commented 4 years ago

Hmm, I see.. that way also works if I define the name and use it when constructing. I use it this way so I can encapsulate it in each module. And about reorder, that's more or less my requirements too. My Saga modules should be able to be reused and reordered based on some flows without much change.

defmodule Api.OnboardingStatusController do
alias Saga.{Status, Merchant, WorkerFlow}
...
    Sage.new()
    |> Status.maybe_update()
    |> Merchant.get_and_validate_merchant_by_phone()
    |> Merchant.get_and_validate_merchant_by_id_number()
    |> WorkerFlow.maybe_trigger_flow()
    |> Sage.execute(attrs)
...

defmodule Saga.Merchant
   def get_and_validate_merchant_by_phone(sage) do
     Sage.run(sage, :merchant_by_phone, &validate_by_phone/2)
   end

   def get_and_validate_merchant_by_id_number(sage) do
     Sage.run(sage, :merchant_by_id_number, &validate_by_id_number/2)
   end
AndrewDryga commented 4 years ago

@ajengs maybe it would still be better if you would have something like this:

def get_and_validate_merchant_by_phone(sage) do
- Sage.run(sage, :merchant_by_phone, &validate_by_phone/2)
+ Sage.run(sage, :merchant_by_phone, &validate_by_phone(&1.phone, &2))
end

It does not look as nice but it is explicit and you don't need to go very deep to understand dependencies between sage steps, does not require you to make sure that validate_by_phone/2 only accepts phone and nothing else (protecting from reordering problem) and still gives you lots of freedom to accept multiple effects whenever you need.

ajengs commented 4 years ago

Got it. I can pass the name when building the sage and pass the it to the next step 👌 And you're right, now I have requirement to use effects from multiple sage steps, it's easier this way. Thanks @AndrewDryga !