railway oriented programming in elixir

24
Railway Oriented Programming in Elixir 03/2017 Erlang & Elixir Ireland @ Zendesk Dublin @mustafaturan

Upload: mustafa-turan

Post on 05-Apr-2017

116 views

Category:

Software


11 download

TRANSCRIPT

Page 1: Railway Oriented Programming in Elixir

Railway Oriented Programming in Elixir

03/2017Erlang & Elixir Ireland@ Zendesk Dublin

@mustafaturan

Page 2: Railway Oriented Programming in Elixir

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

Page 3: Railway Oriented Programming in Elixir

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

Page 4: Railway Oriented Programming in Elixir

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?

Page 5: Railway Oriented Programming in Elixir

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

Page 6: Railway Oriented Programming in Elixir

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

Page 7: Railway Oriented Programming in Elixir

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

Page 8: Railway Oriented Programming in Elixir

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 ?

Page 9: Railway Oriented Programming in Elixir

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"

Page 10: Railway Oriented Programming in Elixir

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:

Page 11: Railway Oriented Programming in Elixir

Information Tuples{:ok, some_data}

{:error, some_error_info}

Page 12: Railway Oriented Programming in Elixir

‘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()

Page 13: Railway Oriented Programming in Elixir

If Elseif email_exist?(“[email protected]”) do

do_sth()

{:ok, some_value}

else

{:error, %{email: “Not exist”}}

end

Page 14: Railway Oriented Programming in Elixir

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

Page 15: Railway Oriented Programming in Elixir

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

Page 16: Railway Oriented Programming in Elixir

Railway Oriented Programming with ‘WITH’‘with’ clause

- Matches patterns with result of the function

- if matches executes next

- Else executes else block

Page 17: Railway Oriented Programming in Elixir

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

Page 18: Railway Oriented Programming in Elixir

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

Page 19: Railway Oriented Programming in Elixir

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

Page 20: Railway Oriented Programming in Elixir

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

Page 21: Railway Oriented Programming in Elixir

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

Page 23: Railway Oriented Programming in Elixir

QUESTIONS

Page 24: Railway Oriented Programming in Elixir

THANK YOU