Railway Oriented Programming in Elixir

Railway Oriented Programming in Elixir

Error handling techniques in Elixir


Mustafa Turan

March 28, 2017


  1. Railway Oriented Programming in Elixir 03/2017 Erlang & Elixir Ireland

    @ Zendesk Dublin @mustafaturan
  2. Summary Railway 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
  3. 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
  4. 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?
  5. 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
  6. Raise Errors Simple 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
  7. 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 Errors Catch With Pattern Matching in Rescue Clause
  8. 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 After Need something to execute on both failure and success cases - Increment metrics ?
  9. 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"
  10. 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:
  11. Information Tuples {:ok, some_data} {:error, some_error_info}

  12. ‘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()
  13. If Else if email_exist?(“sample@sample.com”) do do_sth() {:ok, some_value} else {:error,

    %{email: “Not exist”}} end
  14. Nested Conditions / If Else and Case Cond maybe_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
  15. Railway Oriented Programming email_exists? pwd_correct? email_confirm? otp_valid? insert_session Sample: User

    login with several checks result input email_exists? pwd_correct? email_confirm? otp_valid? insert_session result input
  16. Railway Oriented Programming with ‘WITH’ ‘with’ clause - Matches patterns

    with result of the function - if matches executes next - Else executes else block
  17. 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
  18. Railway Oriented Programming with Pattern Matching on Function Level and

    Pipes Pattern matching - on function level Pipe operator - to pass result of function to next function's first argument
  19. 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
  20. 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
  21. 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
  22. SOURCES 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