railway oriented programming in elixir
Post on 05-Apr-2017
116 Views
Preview:
TRANSCRIPT
Railway Oriented Programming in Elixir
03/2017Erlang & Elixir Ireland@ Zendesk Dublin
@mustafaturan
SummaryRailway Oriented Programming is a clean functional approach for handling errors
- Error Handling in Elixir
- Error Handling with Railway Oriented Programming Approach
- Tip: The bang!
- Questions
Error Handling in Elixir- Let it crash?
- Try Rescue After blocks
- Try Catch blocks
- Trap exit signal
- Information Tuples with Conditionals
- {:error, …} or {:ok, …}
- If else / case cond do
- Railway Oriented Programming
- WITH Clause (Elixir 1.2)
- Pattern Matching in Function Level
Let it crash?Ariane 5 flight 501 (1996)
- Engines exploited by a bug
- that was not found in previous models :)
- Do you want to cause an exploit?
Let it crash?The purpose of ‘Let it Crash!’
- Fresh start
- Generally should not save the state and reload
- Consider it like a restart your computer
- Sometimes save data
- Sometimes not
- Ask this question to yourself:
- Is restarting the process solve my problem, or NOT?
- Samples (it depends!):
- DB connection error, crash, then restart on init
- Retryable
Raise ErrorsSimple Raise
iex> raise "Oh no error is coming!"
** (RuntimeError) Oh no error is coming!
Simple Raise with Type
iex> raise ArgumentError, message: "Invalid arg!"
** (ArgumentError) Invalid arg!
Simple Raise with Type
defmodule SampleError do
defexception message: "this is special one"
end
try do
opts
|> Keyword.fetch!(:file_path)
|> File.read!
|> SampleModule.just_do_it!
rescue
e in KeyError -> IO.puts "missing :file_path option"
e in File.Error -> IO.puts "unable to read file"
e in SampleError -> IO.inspect e
end
Rescue ErrorsCatch With Pattern Matching in Rescue Clause
try do
opts
|> Keyword.fetch!(:file_path)
rescue
e in KeyError -> IO.puts "missing :file_path option"
after
IO.puts "I will print at both case!"
end
Rescue Errors and AfterNeed something to execute on both failure and success cases
- Increment metrics ?
Throw and Catch- Not common
- You can even catch the ‘exit’ signals
- You can pass the value and fetch that value on catch block
try do
for x <- 0..10 do
if x == 2, do: throw(x)
IO.puts(x)
end
catch
x -> "Caught: #{x}"
end
0
1
"Caught: 2"
Trap Exit
def handle_info({:EXIT, _pid, reason}, state),
do: {:stop, reason, state}
def terminate(reason, state) do
# do sth in here
:ok
end
Inside your init() function:
Process.flag(:trap_exit, true)
Inside your module:
Information Tuples{:ok, some_data}
{:error, some_error_info}
‘If Else’ and ‘Case Cond’ Clauses- No ‘return’ clause in Elixir
- Simple Checks
- email_exists?(email)
- Complex Controls (Sample: User.Login)
- validate_email_exists()
- validate_email_password_match()
- validate_email_confirmation()
- validate_one_time_password()
- insert_session_token()
If Elseif email_exist?(“sample@sample.com”) do
do_sth()
{:ok, some_value}
else
{:error, %{email: “Not exist”}}
end
Nested Conditions / If Else and Case Condmaybe_user = fetch_user(email)
case maybe_user do
{:ok, user} ->
case password_match(user, password) do
{:ok, true} ->
case is_confirmed?(user) do
...
end
{:error, msg} ->
{:error, msg}
end
{:error, msg} ->
{:error, msg}
end
Railway Oriented Programming
email_exists? pwd_correct? email_confirm? otp_valid? insert_session
Sample: User login with several checks
resultinput
email_exists? pwd_correct? email_confirm? otp_valid? insert_session
result
input
Railway Oriented Programming with ‘WITH’‘with’ clause
- Matches patterns with result of the function
- if matches executes next
- Else executes else block
Railway Oriented Programming with ‘WITH’with {:ok, user} <- fetch_user(email),
{:ok, true} <- password_match(user, password),
{:ok, ...} <- … do
sth()
else
{:error, error} -> handle_me(error)
end
Railway Oriented Programming with Pattern Matching on Function Level and PipesPattern matching
- on function level
Pipe operator
- to pass result of function to next function's first argument
Railway Oriented Programming with Pattern Matching on Function Level and Pipes def process(params) do
params
|> validate_email_exists()
|> validate_email_password_match()
|> validate_email_confirmation()
|> validate_one_time_password()
|> insert_session_token()
end
defp validate_email_password_match({:error, opts}), do: {:error, opts}
defp validate_email_confirmation({:error, opts}), do: {:error, opts}
defp validate_one_time_password({:error, opts}), do: {:error, opts}
defp insert_session_token({:error, opts}), do: {:error, opts}
# You can also create
a macro to create
error catching
functions
automatically
Tip: The bang!Not a rule BUT:
- In Elixir generally functions has two forms
- without bang!
- some_function(....)
- {:ok, result}
- {:error, “Some message or any type of data”}
- with bang!
- some_function!(...)
- result
- Raise an error
- Elixir ‘bang’ package
- @bang {[list_of_func_name_arg_count_tuples], {CallbackModule, :callback_fn}}
- https://github.com/mustafaturan/bang
Tip: The bang!defmodule SomeBangModule do
def raise_on_clause({:ok, some_val}),
do: some_val
def raise_on_clause({:error, err}),
do: raise err
end
defmodule OtherModule do
@bang {[do_sth: 1], {SomeBangModule, :raise_on_clause}}
def do_sth({:ok, some_val}) do
if some_val > 0, do: {:ok, “Good!”}, else: {:error, “Invalid”}
end
end
SOUR
CES Blog post:
https://medium.com/@mustafaturan/railway-oriented-programmin
g-in-elixir-with-pattern-matching-on-function-level-and-pipelining-
e53972cede98
https://hexdocs.pm/elixir/Kernel.SpecialForms.html#with/1
Tutor:
https://elixirschool.com/lessons/advanced/error-handling/
Robs’ talk:
https://vimeo.com/113707214
QUESTIONS
THANK YOU
top related