Links:
How do we stop execution of functions if one fails?
def process_checkout(order) do
order
|> update_order()
|> capture_payment()
|> send_notification_email()
|> update_stock_levels()
end
Tagged tuples to represent success and error states
{:ok, _} | {:error, }
defp update_order({:ok, order}) do
# do stuff
{:ok, order}
end
defp update_order({:error, reason}) do
# do stuff
{:error, reason}
end
...repeat for each function
A different way...
defmodule Result do
def bind({:ok, value}, func) do
func.(value)
end
def bind({:error, _} = failure, _) do
failure
end
end
using bind (a monad)
import Result
def process_checkout(order) do
order
|> bind(&update_order/1)
|> bind(&capture_payment/1)
|> bind(&send_notification_email/1)
|> bind(&update_stock_levels/1)
end
The Elixir Way
def process_checkout(order) do
with {:ok, order} <- update_order(order),
{:ok, order} <- capture_payment(order),
{:ok, order} <- send_notification_email(order),
{:ok, order} <- update_stock_levels(order),
do: {:ok, order}
else
{:error, %Changeset{} = cs} -> handle_error(cs)
{:error, reason} -> handle_other_reason(reason)
end
An example with Ecto
def process_checkout(order) do
Repo.transaction(fn ->
do_process_checkout(order)
|> case do
{:ok, order} -> order
{:error, cs} -> Repo.rollback(cs)
end)
end
defp do_process_checkout(order) do
# Everything here returns an Ecto.Changeset
with {:ok, order} <- update_order(order),
{:ok, order} <- capture_payment(order),
{:ok, order} <- send_notification_email(order),
{:ok, order} <- update_stock_levels(order),
do: {:ok, order}
end