Slide 1

Slide 1 text

State Machines State Machines in Elixir in Elixir with Machinery @joaomdmoura

Slide 2

Slide 2 text

State Machines State Machines in Elixir in Elixir with Machinery @joaomdmoura Avoiding Complexity in Software Design Software Design

Slide 3

Slide 3 text

State Machines State Machines in Elixir in Elixir with Machinery @joaomdmoura That time I fell from a Stakeboard Stakeboard

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

This shit is easy

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

things can get things can get complex complex really fast really fast

Slide 12

Slide 12 text

design design matters matters

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

Quality code has low cost for changes

Slide 15

Slide 15 text

state state machines machines

Slide 16

Slide 16 text

Joao Moura Engineering Manager and Speaker @joaomdmoura facebook.com/joaomdmouradev

Slide 17

Slide 17 text

state state what? what?

Slide 18

Slide 18 text

It is an abstract machine that can be in exactly one of a finite number of states at any given time.

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

empty Shopping Cart Shopping Cart filled payed abandoned

Slide 21

Slide 21 text

empty Shopping Cart Shopping Cart filled payed abandoned add item leave checkout

Slide 22

Slide 22 text

Where will this logic live?

Slide 23

Slide 23 text

No content

Slide 24

Slide 24 text

empty lock items add item in stock? no yes filled

Slide 25

Slide 25 text

State State State State empty lock items add item in stock? no yes filled

Slide 26

Slide 26 text

Guard Guard Callbacks Callbacks empty lock items add item in stock? no yes filled

Slide 27

Slide 27 text

empty lock items add item in stock? no yes filled checkout leave payed? no payed yes abandoned unblock items

Slide 28

Slide 28 text

Where will this logic live?

Slide 29

Slide 29 text

No content

Slide 30

Slide 30 text

Shopping Carts Shopping Carts Users States Users States Orders Orders Messages Messages

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

No content

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

20 20 states states

Slide 35

Slide 35 text

No content

Slide 36

Slide 36 text

No content

Slide 37

Slide 37 text

No content

Slide 38

Slide 38 text

No content

Slide 39

Slide 39 text

Elixir Elixir

Slide 40

Slide 40 text

Functions must not Functions must not updates values outside of it updates values outside of it

Slide 41

Slide 41 text

Functions must not Functions must not depend on variables other depend on variables other than its parameters than its parameters

Slide 42

Slide 42 text

"Elixir".downcase # elixir

Slide 43

Slide 43 text

String.downcase("Elixir") # elixir

Slide 44

Slide 44 text

Erlang Erlang

Slide 45

Slide 45 text

gen_statem gen_statem "low-level" implementation for "low-level" implementation for state machines as processes state machines as processes

Slide 46

Slide 46 text

defmodule YourProject.UserStateMachine do @behaviour :gen_statem @name :user_state_machine def start_link do :gen_statem.start_link({:local, @name}, __MODULE__, [], []) end def init([]) do {:ok, :created, %{name: "Joe Doe"}} end def callback_mode do :state_functions end def fill_data do :gen_statem.call(@name, :fill_data) end def completed({:call, from}, :fill_data, data) do {:next_state, :completed, data, [{:reply, from, :ok}]} end end

Slide 47

Slide 47 text

defmodule YourProject.UserStateMachine do @behaviour :gen_statem @name :user_state_machine def start_link do :gen_statem.start_link({:local, @name}, __MODULE__, [], []) end def init([]) do {:ok, :created, %{name: "Joe Doe"}} end def callback_mode do :state_functions end def fill_data do :gen_statem.call(@name, :fill_data) end def completed({:call, from}, :fill_data, data) do {:next_state, :completed, data, [{:reply, from, :ok}]} end end

Slide 48

Slide 48 text

defmodule YourProject.UserStateMachine do @behaviour :gen_statem @name :user_state_machine def start_link do :gen_statem.start_link({:local, @name}, __MODULE__, [], []) end def init([]) do {:ok, :created, %{name: "Joe Doe"}} end def callback_mode do :state_functions end def fill_data do :gen_statem.call(@name, :fill_data) end def completed({:call, from}, :fill_data, data) do {:next_state, :completed, data, [{:reply, from, :ok}]} end end

Slide 49

Slide 49 text

defmodule YourProject.UserStateMachine do @behaviour :gen_statem @name :user_state_machine def start_link do :gen_statem.start_link({:local, @name}, __MODULE__, [], []) end def init([]) do {:ok, :created, %{name: "Joe Doe"}} end def callback_mode do :state_functions end def fill_data do :gen_statem.call(@name, :fill_data) end def completed({:call, from}, :fill_data, data) do {:next_state, :completed, data, [{:reply, from, :ok}]} end end

Slide 50

Slide 50 text

defmodule YourProject.UserStateMachine do @behaviour :gen_statem @name :user_state_machine def start_link do :gen_statem.start_link({:local, @name}, __MODULE__, [], []) end def init([]) do {:ok, :created, %{name: "Joe Doe"}} end def callback_mode do :state_functions end def fill_data do :gen_statem.call(@name, :fill_data) end def completed({:call, from}, :fill_data, data) do {:next_state, :completed, data, [{:reply, from, :ok}]} end end

Slide 51

Slide 51 text

defmodule YourProject.UserStateMachine do @behaviour :gen_statem @name :user_state_machine def start_link do :gen_statem.start_link({:local, @name}, __MODULE__, [], []) end def init([]) do {:ok, :created, %{name: "Joe Doe"}} end def callback_mode do :state_functions end def fill_data do :gen_statem.call(@name, :fill_data) end def completed({:call, from}, :fill_data, data) do {:next_state, :completed, data, [{:reply, from, :ok}]} end end

Slide 52

Slide 52 text

defmodule YourProject.UserStateMachine do @behaviour :gen_statem @name :user_state_machine def start_link do :gen_statem.start_link({:local, @name}, __MODULE__, [], []) end def init([]) do {:ok, :created, %{name: "Joe Doe"}} end def callback_mode do :state_functions end def fill_data do :gen_statem.call(@name, :fill_data) end def completed({:call, from}, :fill_data, data) do {:next_state, :completed, data, [{:reply, from, :ok}]} end end

Slide 53

Slide 53 text

fsm fsm Fsm is pure functional finite Fsm is pure functional finite state machine state machine

Slide 54

Slide 54 text

defmodule BasicFsm do use Fsm, initial_state: :stopped defstate stopped do # opens the state scope defevent run do # defines event next_state(:running) # transition to next state end end defstate running do defevent stop do next_state(:stopped) end end end

Slide 55

Slide 55 text

Machinery Machinery

Slide 56

Slide 56 text

Declaring Declaring States States

Slide 57

Slide 57 text

defmodule YourProject.UserStateMachine do use Machinery, states: ["created", "partial", "complete"], transitions: %{ "created" => ["partial", "complete"], "partial" => "completed" } end

Slide 58

Slide 58 text

defmodule YourProject.UserStateMachine do use Machinery, states: ["created", "partial", "complete"], transitions: %{ "created" => ["partial", "complete"], "partial" => "completed" } end

Slide 59

Slide 59 text

defmodule YourProject.UserStateMachine do use Machinery, states: ["created", "partial", "complete"], transitions: %{ "created" => ["partial", "complete"], "partial" => "completed" } end

Slide 60

Slide 60 text

defmodule YourProject.UserStateMachine do use Machinery, states: ["created", "partial", "complete"], transitions: %{ "created" => ["partial", "complete"], "partial" => "completed" } end

Slide 61

Slide 61 text

Machinery.transition_to(your_struct, UserStateMachine, "next_state") # {:ok, updated_struct}

Slide 62

Slide 62 text

Machinery.transition_to(your_struct, UserStateMachine, "next_state") # {:ok, updated_struct}

Slide 63

Slide 63 text

Machinery.transition_to(your_struct, UserStateMachine, "next_state") # {:ok, updated_struct}

Slide 64

Slide 64 text

Guard Guard Functions Functions

Slide 65

Slide 65 text

def guard_transition(struct, "complete") do Map.get(struct, :missing_fields) == false end

Slide 66

Slide 66 text

Before and after Before and after callbacks callbacks

Slide 67

Slide 67 text

def before_transition(struct, "complete") do ... struct end def after_transition(struct, "complete") do ... struct end

Slide 68

Slide 68 text

shopping shopping cart cart

Slide 69

Slide 69 text

defmodule FakeProject.ShoppingCartMachine do use Machinery, states: ["empty", "filled", "payed", "abandoned"], transitions: %{ "empty" => "filled", "filled" => ["payed", "abandoned"] } def guard_function(cart, "filled") do Item.has_stock?(cart.item) end def guard_function(cart, "payed") do Payment.status(cart) == :confirmed end def before_transition(cart, "filled") do Item.lock_form_cart(cart) cart end def after_transition(cart, "abadonned") do Item.unlock_form_cart(cart) cart end end

Slide 70

Slide 70 text

defmodule FakeProject.ShoppingCartMachine do use Machinery, states: ["empty", "filled", "payed", "abandoned"], transitions: %{ "empty" => "filled", "filled" => ["payed", "abandoned"] } def guard_function(cart, "filled") do Item.has_stock?(cart.item) end def guard_function(cart, "payed") do Payment.status(cart) == :confirmed end def before_transition(cart, "filled") do Item.lock_form_cart(cart) cart end def after_transition(cart, "abadonned") do Item.unlock_form_cart(cart) cart end end

Slide 71

Slide 71 text

defmodule FakeProject.ShoppingCartMachine do use Machinery, states: ["empty", "filled", "payed", "abandoned"], transitions: %{ "empty" => "filled", "filled" => ["payed", "abandoned"] } def guard_function(cart, "filled") do Item.has_stock?(cart.item) end def guard_function(cart, "payed") do Payment.status(cart) == :confirmed end def before_transition(cart, "filled") do Item.lock_form_cart(cart) cart end def after_transition(cart, "abadonned") do Item.unlock_form_cart(cart) cart end end

Slide 72

Slide 72 text

defmodule FakeProject.ShoppingCartMachine do use Machinery, states: ["empty", "filled", "payed", "abandoned"], transitions: %{ "empty" => "filled", "filled" => ["payed", "abandoned"] } def guard_function(cart, "filled") do Item.has_stock?(cart.item) end def guard_function(cart, "payed") do Payment.status(cart) == :confirmed end def before_transition(cart, "filled") do Item.lock_form_cart(cart) cart end def after_transition(cart, "abadonned") do Item.unlock_form_cart(cart) cart end end

Slide 73

Slide 73 text

bonus bonus

Slide 74

Slide 74 text

defmodule YourApp.Endpoint do # ... plug Machinery.Plug # ... end config :machinery, interface: true, repo: YourApp.Repo, model: YourApp.User, module: YourApp.UserStateMachine

Slide 75

Slide 75 text

No content

Slide 76

Slide 76 text

No content

Slide 77

Slide 77 text

state machines state machines everywhere everywhere

Slide 78

Slide 78 text

Machinery Machinery

Slide 79

Slide 79 text

things can get things can get complex complex really fast really fast

Slide 80

Slide 80 text

No content

Slide 81

Slide 81 text

@joaomdmoura joaomdmoura.com facebook.com/joaomdmouradev