Slide 1

Slide 1 text

How to Organize your Elixir Projects?

Slide 2

Slide 2 text

Summary — Code organization? — Architecture patterns & Elixir — Layers — MVC — View Components — Bounded Context — Architecture |> Hexagonal |> Onion |> Clean |> ??? — Pragmatic Elixir

Slide 3

Slide 3 text

I'm Ulisses Almeida — Work for The Coingaming Group — living in Estonia — I'm also author of Learn Functional Programming with Elixir

Slide 4

Slide 4 text

Code organization — Files and folders — Modules, functions, and their relationships — Web developer / Server side / Monolith perspective

Slide 5

Slide 5 text

Book table of contents

Slide 6

Slide 6 text

Fiction book

Slide 7

Slide 7 text

Academic book

Slide 8

Slide 8 text

Elixir project . ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── _build ├── config ├── deps ├── doc ├── lib ├── mix.exs ├── mix.lock └── test

Slide 9

Slide 9 text

Elixir project docs

Slide 10

Slide 10 text

Your project architecture should help people find what they are looking for and express what the application does

Slide 11

Slide 11 text

Architecture patterns Your application is not that special

Slide 12

Slide 12 text

Layers We need to start somewhere

Slide 13

Slide 13 text

Layers — They organically emerges — Top to bottom dependency and dataflow

Slide 14

Slide 14 text

Layers in Phoenix/Elixir lib/hello_web lib/hello 1. Web Layer: hello_web 2. Domain Layer: hello

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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?

Slide 17

Slide 17 text

MVC A structured presentation layer

Slide 18

Slide 18 text

MVC — Model-View-Controller & variations — User interface centric

Slide 19

Slide 19 text

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)

Slide 20

Slide 20 text

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?

Slide 21

Slide 21 text

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?

Slide 22

Slide 22 text

View Components A new (and old) way of organizing your presentation layer

Slide 23

Slide 23 text

View components — Small, independent and self-contained "presentation" files

Slide 24

Slide 24 text

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"""
<%= @user.name %>
""" end end — Check out Surface! — Does it fix the accidental complexity of controllers, views, templates, and assets organization?

Slide 25

Slide 25 text

View components — View Component: put a little bit of everything there — How your domain should be organized? — Where I put external service wrappers?

Slide 26

Slide 26 text

Bounded Context Let's give some ❤ to your domain

Slide 27

Slide 27 text

Bounded Context

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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 ( ?)

Slide 30

Slide 30 text

Like any other Elixir project ( ! ?) — Has public modules, and private modules: @moduledoc false

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

"Namespacing" with contexts Hello.Accounts Hello.Accounts.User Hello.Sales.Customer Hello.Support.Customer Remember that Elixir doesn't have any feature related to namespace.

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

@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)

Slide 36

Slide 36 text

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.

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

Remember: your project organization should help people find what they are looking for Tip: Keep the default !

Slide 42

Slide 42 text

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?

Slide 43

Slide 43 text

Hexagonal Architecture & Variations Isolate your application core logic

Slide 44

Slide 44 text

Hexagonal Architecture — New thoughts how organize your app — Application vs outside world — Alistair Cockburn: https://alistair.cockburn.us/hexagonal- architecture/

Slide 45

Slide 45 text

⬢ Hexagonal Elixir ├── lib └── hello (application) └── hello_web (external)

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

⬢ 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

Slide 48

Slide 48 text

Onion Architecture — More layers in the "area" — Coupling towards to center — Jefrey Palermo https://jeffreypalermo.com/2008/07/the-onion- architecture-part-1/

Slide 49

Slide 49 text

! Onion Elixir ├── lib └── hello (app + domain services + model) └── hello_web (user interface) └── mix (user interface) └── paypal (infrastructure) └── twitter (infrastructure) └── tesla_extensions.ex ("library, utils")

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

✨ 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?

Slide 52

Slide 52 text

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)

Slide 53

Slide 53 text

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.

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

Pragmatic Elixir My personal opinions !

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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"

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

https://twitter.com/jdvogt/status/1319065447346573313

Slide 65

Slide 65 text

Sometimes you need to break the consistency rules to maintain clarity!

Slide 66

Slide 66 text

Thanks!

Slide 67

Slide 67 text

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