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.

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