10. Let it crash!
‣ Accept the fact that things fail
‣ Focus on the happy path
‣ Make failures more predictable
11. Let it crash!
‣ Separate the logic and error handling
‣ When something is wrong, let the
process crash and let another one
handle it (e.g. by restarting)
31. Heart
## vm.args
## Heartbeat management; auto-restarts VM if it
##dies or becomes unresponsive
## (Disabled by default use with caution!)
-heart
-env HEART_COMMAND ~/heart_command.sh
47. Restoring the state
def init(_) do
state = restore_state()
{:ok, state}
end
def terminate(_reason, state) do
save_state(state)
end
http://mkaszubowski.pl/2017/09/02/On-Restoring-Process-State.html
59. def update_name(user, name) do
case update(user, %{name: name}) do
{:ok, user} {:ok, user}
{:error, reason} {:error, reason}
end
end
60. def update_name(user, name) do
case update(user, %{name: name}) do
{:ok, user} {:ok, user}
{:error, reason} {:error, reason}
end
end
61. def update_name(user, name) do
case update(user, %{name: name}) do
{:ok, user} {:ok, user}
{:error, reason} {:error, reason}
end
end
‣ Do you know how to handle reason?
‣ Is {:error, reason} even possible?
‣ Fatal or acceptable error?
62. ‣ What is likely to happen?
‣ What is an acceptable error?
‣ What do I know how to handle?
64. def update_name(user, name) do
case update(user, %{name: name}) do
{:ok, user} {:ok, user}
{:error, %{errors: [username: "cannot be blank"]}}
{:error, :blank_username}
end
end
Acceptable error
65.
66. def update_description(transaction, user) do
with
%{receipt: receipt} transaction,
false is_nil(receipt),
{:ok, %{"id" id} Poison.decode(receipt),
{:ok, %{status: 200, body: body}} Adapter.update(id, user)
{:ok, _} update_db_record(id, body)
do
:ok
end
end
67. def update_description(transaction, user) do
Task.Supervisor.start_child(MyApp.TaskSupervisor, fn
with
%{receipt: receipt} transaction,
false is_nil(receipt),
{:ok, %{"id" id} Poison.decode(receipt),
{:ok, %{status: 200, body: body}} Adapter.update(id, user)
{:ok, _} update_db_record(id, body)
do
:ok
end
end)
end
68. def update_description(transaction, user) do
Task.Supervisor.start_child(MyApp.TaskSupervisor, fn
with
%{receipt: receipt} transaction,
false is_nil(receipt),
{:ok, %{"id" id} Poison.decode(receipt),
{:ok, %{status: 200, body: body}} Adapter.update(id, user)
{:ok, _} update_db_record(id, body)
do
:ok
end
end)
end
69. def update_description(transaction, user) do
Task.Supervisor.start_child(MyApp.TaskSupervisor, fn
with
%{receipt: receipt} transaction,
false is_nil(receipt),
{:ok, %{"id" id} Poison.decode(receipt),
{:ok, %{status: 200, body: body}} Adapter.update(id, user)
{:ok, _} update_db_record(id, body)
do
:ok
end
end)
end
70. def update_description(transaction, user) do
Task.Supervisor.start_child(MyApp.TaskSupervisor, fn
%{"id" transaction_id} = Poison.decode!(receipt)
{:ok, %{body: body}} = Adapter.update(transaction_id, user)
{:ok, _} = update_db_record(transaction_id, body)
end
end
78. def handle_info(:do_work, state) do
with {:ok, data} ServiceA.fetch_data(),
{:ok, other_data} ServiceB.fetch_data()
do
do_some_work(data, other_data)
end
Process.send_after(self(), :do_work, @one_hour)
{:noreply, state}
end
80. defmodule ServiceA do
def fetch_data() do
{:ok, [1, 2, 3, 4, 5]}
end
end
defmodule ServiceA do
def fetch_data() do
[1, 2, 3, 4, 5]
end
end
81. iex(4)> with {:ok, data} ServiceA.fetch_data, do: :ok
[1, 2, 3, 4, 5]
iex(6)> {:ok, data} = ServiceA.fetch_data()
** (MatchError) no match of right hand side value: [1, 2, 3, 4, 5]
82. [error] GenServer Fail.Worker terminating
** (MatchError) no match of right hand side value: [1, 2, 3, 4, 5]
(fail) lib/fail/worker.ex:30: Fail.Worker.handle_info/2
(stdlib) gen_server.erl:615: :gen_server.try_dispatch/4
(stdlib) gen_server.erl:681: :gen_server.handle_msg/5
(stdlib) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
Last message: :do_work
State: nil
84. ‣ Things will fail
‣ Fault tolerance isn't free
‣ Know your tools
‣ Think what you can handle
‣ Don't try to handle every possible error
‣ Think about supervision structure