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

Como organizar seu projeto Elixir?

Como organizar seu projeto Elixir?

Essa palestra irá explorar várias práticas de organização de diretórios e arquivos em projetos Elixir. Vamos explorar alguma práticas comuns não só em Elixir como no desenvolvimento de software em geral, como por exemplo, MVC, Camadas, Contextos, arquitetura hexagonal e componentes. Aqui, iremos discutir vantagens e desvantagens de cada uma das práticas e evidenciar o que os projetos Elixir Open Source tem adotado. No final da palestra, você será capaz de comparar e decidir melhor que práticas adotar no seu projeto em Elixir.

Avatar for Ulisses Almeida

Ulisses Almeida

November 06, 2020
Tweet

More Decks by Ulisses Almeida

Other Decks in Programming

Transcript

  1. Summary — Code organization? — Architecture patterns & Elixir —

    Layers — MVC — View Components — Bounded Context — Architecture |> Hexagonal |> Onion |> Clean |> ??? — Pragmatic Elixir
  2. I'm Ulisses Almeida — Work for The Coingaming Group —

    living in Estonia — I'm also author of Learn Functional Programming with Elixir
  3. Code organization — Files and folders — Modules, functions, and

    their relationships — Web developer / Server side / Monolith perspective
  4. Elixir project . ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├──

    README.md ├── _build ├── config ├── deps ├── doc ├── lib ├── mix.exs ├── mix.lock └── test
  5. Your project architecture should help people find what they are

    looking for and express what the application does
  6. Layers in Phoenix/Elixir — Web Layer depends on Domain —

    Domain layer should not depend on parent layer # hello/application.ex children = [ # ... # Start the Endpoint (http/https) HelloWeb.Endpoint # ... ] ! oops
  7. Layers — Data layer: put your queries ☑ — Business

    layer: put your business logic — Presentation layer: put your HTML and stuff — How each layer should be organized? — Where I put external service wrappers?
  8. MVC In Phoenix/Elixir ├── assets (V) ├── lib │ └──

    hello (M) │ └── hello_web ├── channels (C) ├── controllers (C) ├── templates (V) ├── views (v) ├── endpoint.ex (C) ├── gettext.ex (V) ├── router.ex (C)
  9. MVC In Phoenix/Elixir (please don't) ├── lib └── hello (model)

    └── hello_web ├── controllers ├── endpoint.ex ├── router.ex ├── channels ├── requests (old controlers) ├── views ├── assets ├── gettext.ex ├── templates ├── posts ├── post_view.ex ├── index.html.eex — would it make a newcomer life easier or harder to understand?
  10. MVC — Model: put your business and queries — Controllers:

    put your event orchestration logic(sometimes business logic " ) ☑ — Views: put your HTML and stuff ☑ — How your model should be organized? — Where I put external service wrappers?
  11. View components in Elixir — React, VueJS, and etc lives

    in assets folder — Phoenix LiveView in lib/yourapp_web/live defmodule UserComponent do use Phoenix.LiveComponent def render(assigns) do ~L""" <div id="user-<%= @id %>" class="user"><%= @user.name %></div> """ end end — Check out Surface! — Does it fix the accidental complexity of controllers, views, templates, and assets organization?
  12. View components — View Component: put a little bit of

    everything there — How your domain should be organized? — Where I put external service wrappers?
  13. Bounded Context — Organize a large domain in smaller groups

    (bounded context) — Same entity name can appear in different contexts — Why? A unified domain model for whole organization isn't cost- effective https://martinfowler.com/bliki/BoundedContext.html
  14. Bounded Context vs Elixir/Phoenix contexts — We have Phoenix contexts

    (isn't by the book as Bounded Context) — The context module is work as public API/entrypoint — Works like any other Elixir project ( ?)
  15. Like any other Elixir project ( ! ?) — Has

    public modules, and private modules: @moduledoc false
  16. Expanding our domain model ├── lib └── hello.ex └── hello

    └── accounts └── user.ex └── sales └── customer.ex └── product.ex └── territory.ex └── support └── customer.ex └── product.ex └── ticket.ex └── accounts.ex └── sales.ex └── support.ex └── repo.ex
  17. Personally, I like the entrypoint modules inside their folder ├──

    lib └── hello └── hello.ex └── repo.ex └── accounts └── accounts.ex └── user.ex └── sales └── sales.ex └── customer.ex └── product.ex └── territory.ex └── support └── customer.ex └── product.ex └── ticket.ex
  18. How context modules looks like: @doc """ Gets a user

    by email and password. """ @spec get_user_by_email_and_password(String.t(), String.t()) :: User.t | nil def get_user_by_email_and_password(email, password) when is_binary(email) and is_binary(password) do user = Hello.Repo.get_by(User, email: email) if User.valid_password?(user, password) and User.active?(user), do: user end # for public functions, but internal to context @doc false
  19. @doc """ Gets a user by email and password. """

    @spec get_user_by_email_and_password(String.t(), String.t()) :: User.t | nil def get_user_by_email_and_password(email, password) when is_binary(email) and is_binary(password) do user = Hello.Repo.get_by(User, email: email) if User.valid_password?(user, password) and User.active?(user), do: user end — Note that context module can dispatch logic its "internal" modules — But the internal module should not depend on context module (risky of compilation deadlock) — Common shareable logic inside of a context needs to create a sibling internal module — A context can depend on sibling context modules like "Repo", or "Sales" — But you should avoid cyclic dependencies (compilation deadlock)
  20. How far should we go with contexts? — Should all

    schema/struct modules be hidden? Answer: Remember Ecto.Changeset, Ecto.Query... — Should my AppWeb only be able to access App module? Answer: Remember Ecto.Changeset, Ecto.Query... — How breakdown a huge context file? Answer: Try to distribute pure functions logic on schemas, and maybe create a subcontext.
  21. How far should we go with contexts? — How avoid

    contexts cyclic dependency? Answer: New context, or a new domain entrypoint function (hello.ex) — What about your GenServers, Agents, Supervisors? Answer: Depends, maybe your context can be also the entrypoint of the supervision tree or you can split in a different context/module
  22. Contexts and the Web dir? └── hello_web ├── controllers ├──

    user_controller.ex HelloWeb.UserController # router.ex scope "/", HelloWeb do pipe_through :browser resources "/users", UserController end
  23. Should be like this? └── hello_web ├── controllers ├── user.ex

    # router.ex scope "/", HelloWeb.Controllers do pipe_through :browser resources "/users", User end
  24. Or like this? └── hello_web ├── users ├── controller.ex ├──

    view.ex ├── templates.ex # router.ex scope "/", HelloWeb do pipe_through :browser resources "/users", Users.Controller end
  25. Phoenix Contexts — Domain folder: how to organize your business

    — It's not about where to put your HTML, and stuff — Where should I put my queries? # — Where I put external service wrappers?
  26. Hexagonal Architecture — New thoughts how organize your app —

    Application vs outside world — Alistair Cockburn: https://alistair.cockburn.us/hexagonal- architecture/
  27. Growing with ⬢ Hexagonal Elixir ├── lib └── hello (application)

    └── hello_web (external) └── mix (external) └── paypal (external) └── twitter (external) └── tesla_extensions.ex ("utils" module for Tesla) — The adapters for external services can live outside of the application core folder — Mix custom commands or other libraries resuable and shareable utilities can live in lib
  28. ⬢ Hexagonal Elixir vs Repo — The Ecto.Repo is an

    excellent database adapter — Should MyApp.Repo be in or out of your application? ├── lib └── hello (application) └── hello_web (external) └── hello_repo.ex (external ?) defmodule HelloRepo do use Ecto.Repo, otp_app: :payments_flask, adapter: Ecto.Adapters.Postgres end
  29. Onion Architecture — More layers in the "area" — Coupling

    towards to center — Jefrey Palermo https://jeffreypalermo.com/2008/07/the-onion- architecture-part-1/
  30. ! Onion Elixir ├── lib └── hello (app + domain

    services + model) └── hello_web (user interface) └── mix (user interface) └── paypal (infrastructure) └── twitter (infrastructure) └── tesla_extensions.ex ("library, utils")
  31. Clean Architecture — Different naming but follow the same concepts

    Robert C. Martin, https://blog.cleancoder.com/uncle-bob/2012/08/13/the- clean-architecture.html
  32. ✨ Clean Elixir ├── lib └── hello (use cases +

    entities) └── hello_web (web framework) └── mix (ui) └── paypal (external interface) └── twitter (external interface) └── tesla_extensions.ex ("library, utils") — What happens with Repo?
  33. Repo & Queries in ✨ Clean Elixir — All queries

    shouldn't belong to entities or contexts Repo.insert(changeset) # good in contexts modules Repo.get_by(User, email: email) # good in contexts modules # not good in context modules or schema modules query = from p in Post, select: p.title MyRepo.all(query)
  34. One solution: Create dynamic queries in Repo defmodule MyApp.Repo do

    # ... def select_titles_from(queryable) do query = from p in queryable, select: p.title all(query) end end # in module context: Repo.select_titles_from(Post) — Note, the Repo module might get overloaded with many queries. You might want to breakdown in public submodules.
  35. Hexagonal Architecture — Application: put your business services/orchestration — Don't

    mix your HTML, and stuff in application — Don't mix your queries in application — Don't mix your service wrappers in application — Isn't clear the dependency between the components
  36. Pragmatic Elixir — "Elixir library" mindset is good to keep

    in mind: Which inner modules are supposed to be public or private? — Coupling towards to the center of your application — Preserving a pure funcional core
  37. Pragmatic Elixir & Frameworks ├── assets ├── lib └── hello

    └── hello.ex └── application.ex └── repo.ex └── hello_web # ... — Don't fight against your chosen framework — It's fine do some experiments, moving one thing or another per time
  38. Pragmatic Elixir & Lib folder ├── lib └── hello (domain)

    └── hello_web (ui, phoenix) └── mix (ui) └── paypal (external) # sometimes can be a overkill └── twitter (external) └── tesla_extensions.ex ("library utils") — Use more your "lib" folder — Keep independent external code outside of your domain — Treat them as "libraries" — Avoid god modules called "utils"
  39. Pragmatic Elixir & Domain hello └── hello.ex └── repo.ex └──

    user_registration.ex └── accounts └── accounts.ex └── user.ex └── sales └── sales.ex └── customer.ex └── product.ex └── territory.ex └── report.ex └── support └── customer.ex └── product.ex └── ticket.ex
  40. Pragmatic Elixir & Domain hello └── user_registration.ex — Not all

    contexts need structs/schemas, they can have only functions — Your contexts doesn't always need to hide the structs/schemas
  41. Pragmatic Elixir & Domain └── sales └── report.ex — It's

    fine starting your queries as private functions in your context modules — If the queries get too big, you might want to create inner modules, like report.ex
  42. Pragmatic Elixir & Domain — It's kinda ok cross references

    of schema modules between contexts, but be careful with cyclic dependency — Try to keep your functions cohesive and related code together
  43. Pragmatic Elixir — lib/yourapp_web -> framework MVC to organize your

    HTML — lib/yourapp ->:bounded/phoenix contexts & dat — lib/ -> practice hexagonal architecture for independent external stuff code
  44. References — https://hexdocs.pm/ecto/Ecto.html — https://vuejs.org/v2/guide/ — https://hexdocs.pm/phoenixliveview/Phoenix.LiveView.html — https://hex.pm/packages/phoenix —

    https://alistair.cockburn.us/hexagonal-architecture/ — https://jeffreypalermo.com/2008/07/the-onion-architecture-part-1/ — https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean- architecture.html — https://martinfowler.com/bliki/BoundedContext.html