-
Notifications
You must be signed in to change notification settings - Fork 44
feat: added interleave/4 #83
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 1 commit
66cb34a
18f32ed
51516f7
6449ebe
554f537
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -163,6 +163,16 @@ defmodule Sage do | |
|
|
||
| defguardp is_transaction(value) when is_function(value, 2) or is_mfa(value) | ||
|
|
||
| @typedoc """ | ||
| Just like a transaction, although it receives extra argument of the name of the previous transaction | ||
| """ | ||
| @type intermediate_transaction :: | ||
| (effects_so_far :: effects(), attrs :: any(), previous_stage :: stage_name() -> | ||
| {:ok | :error | :abort, any()}) | ||
| | mfa() | ||
|
|
||
| defguardp is_intermediate_transaction(value) when is_function(value, 3) or is_mfa(value) | ||
|
|
||
| @typedoc """ | ||
| Tracer callback, can be a module that implements `Sage.Tracer` behaviour, an anonymous function, or an | ||
| `{module, function, [arguments]}` tuple. | ||
|
|
@@ -437,6 +447,57 @@ defmodule Sage do | |
| def run_async(sage, name, transaction, compensation, opts \\ []), | ||
| do: add_stage(sage, name, build_operation!(:run_async, transaction, compensation, opts)) | ||
|
|
||
| @doc """ | ||
| For a given Sage S with transactions :t1 -> :t2 -> :t3, a call to `interleave(S, :name, f)` | ||
| will yield a saga with transactions :t1 -> :name_1 -> :t2 -> :name_2 -> :t3 -> :name_3. | ||
|
|
||
| This can be useful if you are trying to do a long computation and want to do something with | ||
| the intermediate results, such as logging or persistence. | ||
|
|
||
| Note: | ||
| - This isn't strict interleaving because a transaction is still appended at the end. | ||
| - Calling this function twice with the same name will give a `Sage.DuplicateStageError`, but | ||
| calling the function twice with a different name will compound its effects. | ||
| - Calling this function before the end of your saga definition will mean any stages you add after the | ||
| call to `interleave` will not have the intermediate stages added after them | ||
| """ | ||
| @spec interleave( | ||
| sage :: t(), | ||
| name :: stage_name(), | ||
| intermediate_transaction :: intermediate_transaction(), | ||
| compensation :: compensation() | ||
| ) :: t() | ||
| def interleave(sage, name, intermediate_transaction, compensation \\ :noop) | ||
| when is_intermediate_transaction(intermediate_transaction) and is_compensation(compensation) do | ||
| sage.stages | ||
| |> Enum.reverse() | ||
| |> Enum.with_index(1) | ||
| |> Enum.flat_map(fn {stage, index} -> | ||
| name = String.to_atom("#{name}_#{index}") | ||
| {stage_name, _} = stage | ||
|
|
||
| transaction = | ||
| case intermediate_transaction do | ||
| {m, f, a} -> | ||
| {m, f, [stage_name | a]} | ||
|
|
||
| _ -> | ||
| &intermediate_transaction.(&1, &2, stage_name) | ||
| end | ||
|
|
||
| [stage, {name, build_operation!(:run, transaction, compensation)}] | ||
|
apreifsteck marked this conversation as resolved.
Outdated
|
||
| end) | ||
| |> Enum.reduce(new(), fn stage, sage -> | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you don't need to reconstruct the Sage completely, just update the existing one like so:
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I did this because I didn't want to lose the duplicate stage name checking that
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I took a stab at a refactor, but I'm not sold on it. I am leaning towards thinking that the best approach is to rebuild the saga and refactor later if need be, but let me know what you think of the refactor attempt.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @apreifsteck if we use the suggestion below and namespace the interleaves then there can't be a collisition with the regular stage names, so we can do something like this:
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've implemented your suggestion to use tuples as the stage names. I'm not sure if I understand the advantage of adding a check in this function to look for duplicate stage names. That seems like a separate responsibility that |
||
| case stage do | ||
| {name, {:run, transaction, comp, _opts}} -> | ||
| run(sage, name, transaction, comp) | ||
|
|
||
| {name, {:run_async, transaction, comp, opts}} -> | ||
| run_async(sage, name, transaction, comp, opts) | ||
| end | ||
| end) | ||
| end | ||
|
|
||
| @doc """ | ||
| Executes a Sage. | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| defmodule TestIntermediateTransactionHandler do | ||
| def intermediate_transaction_handler(_effects, _args, previous_stage_name, something_else) do | ||
| {:ok, {previous_stage_name, something_else}} | ||
| end | ||
| end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we also want compensations to be reported in your use case? If so we'll need to add a type for it too or reuse
intermediate_transactionhere.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question. My use case actually doesn't require compensations at all, I just added them for completeness/generality.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should add it for completeness. If there is some reporter on the saga progress, it should also be able to report compensation progress.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So are you saying that an interleaved compensation should be passed the previously run compensation, to mirror the
intermediate_transaction?