Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Lessons Learned From an Elixir OTP Project

Amanda
August 30, 2019

Lessons Learned From an Elixir OTP Project

You started an Elixir project, coming from another language, and you are curious about how everything fits, what are the most common problems encountered, and how is the best way to solve them.

Questions such as: How to organize your tests in a concurrent application? What are the pitfalls when writing process tests? How can we organize our code? How can contexts help us? Which smells can help us realize it's time to refactor our code? And how do we use the language tooling in our favor?

Amanda

August 30, 2019
Tweet

More Decks by Amanda

Other Decks in Technology

Transcript

  1. SELECT * FROM Courses; SELECT * FROM Users WHERE course_id

    = ? SELECT * FROM Users WHERE course_id = ? SELECT * FROM Users WHERE course_id = ? SELECT * FROM Users WHERE course_id = ? SELECT * FROM Users WHERE course_id = ? SELECT * FROM Users WHERE course_id = ? SELECT * FROM Users WHERE course_id = ? SELECT * FROM Users WHERE course_id = ?
  2. –Erlang Documentation “These provide the ability to store very large

    quantities of data in an Erlang runtime system, and to have constant access time to the data.”
  3. This ETS kinda looks like Redis, and Redis I know

    how to use, so why not use this one for free?
  4. There was this one time where we ended up creating

    an ETS with 76GB of memory consumption
  5. test "fetch/1 returns and formats tweets", %{bypass: bypass} do response

    = Jason.encode!([%{"text" => "Elixir Brasil 2019"}]) Bypass.expect(bypass, fn conn -> assert "/1.1/search/tweets.json" == conn.request_path assert "GET" == conn.method Plug.Conn.resp(conn, 200, response) end) tweets = TwitterClient.fetch("http://localhost:#{bypass.port}") assert tweets == [%{"text" => "Elixir Brasil 2019"}] end
  6. test "fetch/1 returns and formats tweets", %{bypass: bypass} do response

    = Jason.encode!([%{"text" => "Elixir Brasil 2019"}]) Bypass.expect(bypass, fn conn -> assert "/1.1/search/tweets.json" == conn.request_path assert "GET" == conn.method Plug.Conn.resp(conn, 200, response) end) tweets = TwitterClient.fetch("http://localhost:#{bypass.port}") assert tweets == [%{"text" => "Elixir Conf 2019"}] end
  7. Mox

  8. test "messages/0 lists all messages from the timeline" do TwitterMock

    |> expect(:fetch, fn -> [%{"text" => "Olá mundo"}] end) assert Timeline.messages() == {:ok, 1} end https://github.com/amandasposito/mox_example
  9. defmodule NewBank.Counter do use GenServer # Client def start_link(opts) do

    GenServer.start_link(__MODULE__, opts, name: Keyword.get(opts, :name)) end def increment() do GenServer.cast(__MODULE__, :increment) end def count() do GenServer.call(__MODULE__, :fetch) end end
  10. defmodule NewBank.CounterServer do ... # Server @impl true def handle_cast(:increment,

    _from) do default = 1 value = :ets.update_counter(:counter_table, "counter_key", 1, default) {:reply, :ok, value} end @impl true def handle_call(:fetch, _from, counter) do {:reply, :ets.lookup(:counter_table, "counter_key"), counter} end end
  11. test "increments and returns the counter current number" do start_supervised!(CounterServer)

    CounterServer.increment() assert CounterServer.count() == 1 end
  12. defmodule NewBank.CounterServer do ... # Server @impl true def handle_cast(:increment,

    _from) do value = Counter.really_complex |> Counter.new_feature |> Counter.we_need_to_implement |> Counter.increment() {:reply, :ok, value} end @impl true def handle_call(:fetch, _from, counter) do value = Counter.different_way |> Counter.fetch {:reply, value, counter} end end
  13. •Everything I've learned in OOP, I'm going to throw away

    in functional programming? •How do I organize my code? •Do all the problems I had in OOP disappear in functional programming? •What about this Context? How do I use it?
  14. •Long functions •Functions that are hard to test •Simple changes

    need to be done in many places •Feature Envy •Context with too many lines •Tight coupling
  15. defmodule NewBank.CreditCard do @moduledoc """ The CreditCard context. """ def

    create do ... end def fetch do ... end def update do ... end def delete do ... end def lists do ... end def fetch_available_limit do .. end end
  16. def fetch_transactions do .. end def count_transactions do ... end

    def total_transactions_by_user do ... end defp confirmations do ... end def list_top_credit_cards do ... end def fetch_pending_transactions do ... end def count_pending_transactions do ... end defp join_association(query, [{association, nested_preload}]) do ... end defp page_transactions do ... end defp where_transaction_has_multiple_disputes do ... end
  17. def fetch_transactions do .. end def fetch_pending_transactions do ... end

    defp join_association(query, [{association, nested_preload}]) do ... end defp page_transactions do ... end defp where_transaction_has_multiple_disputes do ... end