Skip to content
This repository has been archived by the owner on Aug 12, 2022. It is now read-only.

Commit

Permalink
Not call compensation many times (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
nuxlli authored Sep 2, 2020
1 parent ba639ef commit 52cd941
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 61 deletions.
10 changes: 6 additions & 4 deletions lib/journey.ex
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ defmodule Journey do
[
%Step{
spec: spec,
compensation: {compensation, nil},
compensation: {compensation, nil, :not_called},
transaction: {transaction, call(transaction, journey, type)}
}
]
Expand Down Expand Up @@ -116,13 +116,15 @@ defmodule Journey do
%Step{transaction: {_, result}} when is_ok(result) -> false
_ -> true
end) do
rollback(%__MODULE__{journey | state: :failed})
rollback(journey)
else
_ -> journey
end
end

defp rollback(%__MODULE__{steps: steps} = journey) do
journey = %__MODULE__{journey | state: :failed}

steps =
steps
|> Enum.reverse()
Expand All @@ -133,11 +135,11 @@ defmodule Journey do
end

defp call_compensation(
%Step{compensation: {func, _}, transaction: {_, result}} = step,
%Step{compensation: {func, _, :not_called}, transaction: {_, result}} = step,
journey
)
when is_function(func) and is_ok(result) do
%Step{step | compensation: {func, call(func, journey, :sync)}}
%Step{step | compensation: {func, call(func, journey, :sync), :called}}
end

defp call_compensation(step, _journey), do: step
Expand Down
139 changes: 82 additions & 57 deletions test/journey_test.exs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
defmodule JourneyTest do
use ExUnit.Case

require Logger
alias Journey.Step

describe "with a new journey" do
Expand Down Expand Up @@ -48,19 +49,19 @@ defmodule JourneyTest do
assert [
%Step{
transaction: {_, :ok},
compensation: {_, nil}
compensation: {_, nil, :not_called}
},
%Step{
transaction: {_, :ok},
compensation: {_, nil}
compensation: {_, nil, :not_called}
},
%Step{
transaction: {_, :ok},
compensation: {_, nil}
compensation: {_, nil, :not_called}
},
%Step{
transaction: {_, :ok},
compensation: {_, nil}
compensation: {_, nil, :not_called}
}
] = steps
end
Expand Down Expand Up @@ -114,23 +115,27 @@ defmodule JourneyTest do
test "run compensation if sync transaction fail", %{journey: journey} do
result =
journey
|> Journey.run_async({__MODULE__, :test_compensation}, [:ok])
|> Journey.run({__MODULE__, :test_compensation}, [:error])
|> Journey.run_async({__MODULE__, :test_compensation}, [:ok, self()])
|> Journey.run({__MODULE__, :test_compensation}, [:error, self()])
# Step will not add because before step will failed
|> Journey.run_async({__MODULE__, :test_compensation}, [:ok])
|> Journey.run_async({__MODULE__, :test_compensation}, [:ok, self()])

assert %Journey{result: :error, state: :failed, steps: steps} = result

assert [
%Step{
transaction: {_, :ok},
compensation: {_, :ok}
compensation: {_, :ok, :called}
},
%Step{
transaction: {_, :error},
compensation: {_, nil}
compensation: {_, nil, :not_called}
}
] = steps

assert_receive :transaction_called
assert_receive :compensation_called
refute_receive :compensation_called
end

test "run compensation if async transaction fail", %{journey: journey} do
Expand All @@ -141,126 +146,146 @@ defmodule JourneyTest do

result =
journey
|> Journey.run({__MODULE__, :test_compensation}, [{:ok, :any}])
|> Journey.run_async({__MODULE__, :test_compensation}, [:ok])
|> Journey.run_async({__MODULE__, :test_compensation}, [{:ok, :any}])
|> Journey.run({__MODULE__, :test_compensation}, [:ok])
|> Journey.run_async({__MODULE__, :test_compensation}, [fn_sleep])
|> Journey.run_async({__MODULE__, :test_compensation}, [{:error, :any}])
|> Journey.run({__MODULE__, :test_compensation}, [{:ok, :any}, self()])
|> Journey.run_async({__MODULE__, :test_compensation}, [:ok, self()])
|> Journey.run_async({__MODULE__, :test_compensation}, [{:ok, :any}, self()])
|> Journey.run({__MODULE__, :test_compensation}, [:ok, self()])
|> Journey.run_async({__MODULE__, :test_compensation}, [fn_sleep, self()])
|> Journey.run_async({__MODULE__, :test_compensation}, [{:error, :any}, self()])
# Step will not add because before step will failed
|> Journey.run({__MODULE__, :test_compensation}, [{:ok, :any}])
|> Journey.run({__MODULE__, :test_compensation}, [{:ok, :any}, self()])

assert %Journey{result: {:error, :any}, state: :failed, steps: steps} = result

assert [
%Step{
transaction: {_, {:ok, :any}},
compensation: {_, :ok}
compensation: {_, :ok, :called}
},
%Step{
transaction: {_, :ok},
compensation: {_, :ok}
compensation: {_, :ok, :called}
},
%Step{
transaction: {_, {:ok, :any}},
compensation: {_, :ok}
compensation: {_, :ok, :called}
},
%Step{
transaction: {_, :ok},
compensation: {_, :ok}
compensation: {_, :ok, :called}
},
%Step{
transaction: {_, :ok},
compensation: {_, :ok}
compensation: {_, :ok, :called}
},
%Step{
transaction: {_, {:error, :any}},
compensation: {_, nil}
compensation: {_, nil, :not_called}
}
] = steps

# Only call once each transaction
for _ <- 1..6, do: assert_receive :transaction_called
for _ <- 1..5, do: assert_receive :compensation_called
refute_receive :compensation_called
end

test "run compensation if sync transaction raise a exception", %{journey: journey} do
result =
journey
|> Journey.run_async({__MODULE__, :test_compensation}, [:ok])
|> Journey.run({__MODULE__, :test_compensation}, [fn -> raise "Any error" end])
|> Journey.run_async({__MODULE__, :test_compensation}, [:ok, self()])
|> Journey.run({__MODULE__, :test_compensation}, [fn -> raise "Any error" end, self()])
# Step will not add because before step will failed
|> Journey.run_async({__MODULE__, :test_compensation}, [:ok])
|> Journey.run_async({__MODULE__, :test_compensation}, [:ok, self()])

error = {:error, %RuntimeError{message: "Any error"}}
assert %Journey{result: ^error, state: :failed, steps: steps} = result

assert [
%Step{
transaction: {_, :ok},
compensation: {_, :ok}
compensation: {_, :ok, :called}
},
%Step{
transaction: {_, ^error},
compensation: {_, nil}
compensation: {_, nil, :not_called}
}
] = steps

assert_receive :transaction_called
assert_receive :compensation_called
refute_receive :compensation_called
end

test "run compensation if async transaction raise a exception", %{journey: journey} do
result =
journey
|> Journey.run_async({__MODULE__, :test_compensation}, [:ok])
|> Journey.run_async({__MODULE__, :test_compensation}, [fn -> raise "Any error" end])
|> Journey.run_async({__MODULE__, :test_compensation}, [:ok, self()])
|> Journey.run_async({__MODULE__, :test_compensation}, [fn -> raise "Any error" end, self()])
# Step will not add because before step will failed
|> Journey.run({__MODULE__, :test_compensation}, [:ok])
|> Journey.run({__MODULE__, :test_compensation}, [:ok, self()])

error = {:error, %RuntimeError{message: "Any error"}}
assert %Journey{result: ^error, state: :failed, steps: steps} = result

assert [
%Step{
transaction: {_, :ok},
compensation: {_, :ok}
compensation: {_, :ok, :called}
},
%Step{
transaction: {_, ^error},
compensation: {_, nil}
compensation: {_, nil, :not_called}
}
] = steps

assert_receive :transaction_called
assert_receive :compensation_called
refute_receive :compensation_called
end

test "run compensation if await a async transaction ended with timeout", %{journey: journey} do
result =
journey
|> Journey.run_async({__MODULE__, :test_compensation}, [:ok])
|> Journey.run_async({__MODULE__, :test_compensation}, [fn -> :timer.sleep(500) end], 50)
|> Journey.run_async({__MODULE__, :test_compensation}, [:ok, self()])
|> Journey.run_async({__MODULE__, :test_compensation}, [fn -> :timer.sleep(500) end, self()], 50)
# Step will not be added because previous step has failed due to timeout
|> Journey.run({__MODULE__, :test_compensation}, [:ok])

assert %Journey{result: error, state: :failed, steps: steps} = result
assert {:error, {:timeout, %Step{spec: {{__MODULE__, :test_compensation}, _, 50}}}} = error

assert [
%Step{
transaction: {_, :ok},
compensation: {_, :ok}
},
%Step{
transaction: {_, {:error, {:timeout, %Step{}}}},
compensation: {_, nil}
}
] = steps
|> Journey.run({__MODULE__, :test_compensation}, [:ok, self()])

assert %Journey{result: error, state: :failed, steps: steps} = result
assert {:error, {:timeout, %Step{spec: {{__MODULE__, :test_compensation}, _, 50}}}} = error

assert [
%Step{
transaction: {_, :ok},
compensation: {_, :ok, :called}
},
%Step{
transaction: {_, {:error, {:timeout, %Step{}}}},
compensation: {_, nil, :not_called}
}
] = steps

assert_receive :transaction_called
assert_receive :compensation_called
refute_receive :compensation_called
end
end

def test_compensation(result) when is_function(result) do
{
fn -> result.() end,
fn -> :ok end
}
def test_compensation(result, pid) when not is_function(result) do
test_compensation(fn -> result end, pid)
end

def test_compensation(result) do
def test_compensation(result, pid) do
{
fn -> result end,
fn -> :ok end
fn ->
send(pid, :transaction_called)
result.()
end,
fn ->
send(pid, :compensation_called)
:ok
end
}
end

Expand Down

0 comments on commit 52cd941

Please sign in to comment.