Aprendizados de um projeto Elixir OTP

Aprendizados de um projeto Elixir OTP

Elixir Brasil 2019

7c1e5b1b100ab8cfacbe14173437c998?s=128

Amanda

May 26, 2019
Tweet

Transcript

  1. Aprendizados de um projeto Elixir/OTP

  2. → amandasposito.com → speakerdeck.com/amandasposito → linkedin.com/in/amandasposito

  3. careers.plataformatec.com.br

  4. Disclaimer

  5. Como foi começar em um projeto Elixir, vindo de outra

    linguagem?
  6. None
  7. None
  8. None
  9. Como utilizar o ferramental da linguagem a seu favor?

  10. None
  11. Lidar com banco de dados se tornou muito mais comum

  12. Preloads

  13. None
  14. SELECT * FROM Courses; SELECT * FROM Users WHERE course_id

    = ? SELECT * FROM Users WHERE course_id = ? SELECT * FROM Users WHERE course_id = ?
  15. None
  16. Repo.all from c in Course, preload: [:users]

  17. Ecto tem muitas features interessantes que podem te ajudar no

    dia-a-dia
  18. → Changesets

  19. → Changesets → Schemas

  20. → Changesets → Schemas → Ecto.Multi

  21. → Changesets → Schemas → Ecto.Multi → etc.

  22. Lidando com tabelas gigantescas

  23. Qualquer coisa que fossemos alterar no banco era difícil

  24. None
  25. None
  26. use Mix.Task

  27. defmodule Mix.Tasks.Echo do use Mix.Task @impl Mix.Task def run(_args) do

    Mix.shell.info "Hello World" end end
  28. Saber analisar a performance é muito importante

  29. Explain Analyze

  30. EXPLAIN ANALYZE SELECT "courses"."id", "courses"."data" FROM "courses" INNER JOIN "users"

    ON ("courses"."user_id" = "users"."id") WHERE "users".account_id = 4500000;
  31. Gather (cost=847239.21..2458486.98 rows=2 width=16) (actual time=35048.574..45161.127 rows=2 loops=1) Workers Planned:

    2 Workers Launched: 2 -> Hash Join (cost=846239.21..2457486.78 rows=1 width=16) (actual time=32820.132..45130.374 rows=1 loops=3) Hash Cond: (courses.user_id = users.id) -> Parallel Seq Scan on courses (cost=0.00..1040678.17 rows=45193617 width=16) (actual time=0.044..29710.148 rows=33333333 loops=3) -> Hash (cost=846239.20..846239.20 rows=1 width=8) (actual time=8318.392..8318.392 rows=1 loops=3) Buckets: 1024 Batches: 1 Memory Usage: 9kB -> Seq Scan on users (cost=0.00..846239.20 rows=1 width=8) (actual time=819.773..8318.380 rows=1 loops=3) Filter: (account_id = 4500000) Rows Removed by Filter: 49999999 Planning time: 0.421 ms Execution time: 45169.333 ms
  32. None
  33. None
  34. http://blog.plataformatec.com.br/2019/02/migrations-in-databases-with-large-amount-of-data/

  35. Tabelas ETS

  36. "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." — Erlang Documentation
  37. def hello do opts = [ :set, :named_table, :public, read_concurrency:

    true ] table_name = :elixir_brasil :ets.new(table_name, opts) for n <- 1..100 do :ets.insert(table_name, {n, "Key #{n}"}) end end
  38. None
  39. Nesse momento não tinhamos muita visão da quantidade de dados

  40. Velocidade x Quantidade de itens

  41. A estimativa estava em 76GB de consumo de memória por

    máquina
  42. None
  43. Tabelas ETS não possuem muitas opções de otimização como o

    Redis
  44. None
  45. São como se fossem um Hash direto na memória

  46. Consomem a memória disponível da aplicação

  47. None
  48. https://moz.com/devblog/moz-analytics-db-free

  49. None
  50. Testes

  51. Doctest != Test

  52. None
  53. Sua aplicação está se comunicando com o mundo exterior, e

    agora?
  54. None
  55. None
  56. None
  57. None
  58. Como fazer para stubar/mockar uma requisição?

  59. Bypass

  60. None
  61. setup do bypass = Bypass.open {:ok, bypass: bypass} end

  62. 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
  63. 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
  64. def fetch(url \\ "https://api.twitter.com") do {:ok, response} = HTTPoison.get("#{url}/1.1/search/tweets.json") Jason.decode!(response.body)

    end
  65. Quando usar bypass?

  66. Código que precisa realizar requests HTTP

  67. None
  68. Mox

  69. None
  70. None
  71. 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
  72. http://blog.plataformatec.com.br/2015/10/mocks-and-explicit-contracts/

  73. GenServer

  74. Como testar processos?

  75. Testar Callback de GenServer não é uma boa prática

  76. None
  77. GenServer.cast

  78. GenServer.call

  79. https://devonestes.herokuapp.com/unit-tests-in-elixir-part-2

  80. Quais os problemas mais comuns encontrados?

  81. → Tudo que eu aprendi em OOP, vou jogar fora

    em funcional? → Como eu organizo meu código? → Todos os problemas que eu tinha em OOP, somem em funcional? → E esse tal de Context? Comé qui usa?
  82. A probabilidade do código ficar complexo diminui

  83. Mas os problemas ainda existem

  84. Muitos dos problemas que vemos em OOP reconhecemos em FP

  85. → Funções muito grandes

  86. → Funções muito grandes → Funções difíceis de testar

  87. → Funções muito grandes → Funções difíceis de testar →

    Mudanças simples precisam ser feitas em vários lugares
  88. → Funções muito grandes → Funções difíceis de testar →

    Mudanças simples precisam ser feitas em vários lugares → Feature Envy
  89. → Funções muito grandes → Funções difíceis de testar →

    Mudanças simples precisam ser feitas em vários lugares → Feature Envy → Contextos com muitas linhas
  90. → Funções muito grandes → Funções difíceis de testar →

    Mudanças simples precisam ser feitas em vários lugares → Feature Envy → Contextos com muitas linhas → Acoplamento
  91. → Funções muito grandes → Funções difíceis de testar →

    Mudanças simples precisam ser feitas em vários lugares → Feature Envy → Contextos com muitas linhas → Acoplamento
  92. https://youtu.be/eldYot7uxUc

  93. None
  94. Contexts

  95. A idéia é definir limites entre diferentes módulos da aplicação

  96. Com o passar do tempo e a necessidade de interação

    com vários schemas
  97. Os contextos podem ficar maiores do que deveriam

  98. Preste atenção para não manter código ortogonal ao contexto, no

    contexto
  99. Mantenha queries próximas de seu schema

  100. A exceção são queries que lidam com mais de um

    schema
  101. http://devonestes.herokuapp.com/a-proposal-for-context-rules

  102. None
  103. Umbrella Projects

  104. Dentro de um Umbrella app os apps deveriam acessar arquivos

    uns dos outros?
  105. É uma maneira de organizar seu código

  106. None
  107. None