Learning Phoenix and its v1.7.0-rc
for the first time, I’ve made arbitrary changes to phx.new
conventions, mostly merging, colocating, and simplifying. As a learner, I needed it simple.
Because I had no supervision, I wanted to report what and why I did here. And hopefully I’ll see room for improvement from your feedback.
- Colocating verticals: related entities, liveviews and tests under single directory
- Domains:
*_web/*.ex
are merged to related Contexts - Entities: schema, changeset, queries, repo calls in a single module
- top-level
assets/
andrel/
goes underpriv/
"app"
denotes general, main context- Medium-like auth based on access to email address
Colocating verticals
App, web, and test are divided too early in the file system when many of them corresponds 1 on 1. Closest ideas were separated the farthest. They could be separated neatly into different modules, but close in the project structure.
- lib/demo_web/
- test/
! lib/demo
! lib/demo/domain/*.ex
+ lib/demo/domain/*.live.ex
+ lib/demo/domain/*.test.ex
Domains
Yes I made the name up. Domains have a similar concept to Contexts. .ex
files under lib/demo/
and a directory of the same name. But now they don’t call Repo
as much, and accommodate web stuff. They export plugs and on_mount
callbacks.
lib/demo/domain/
+ lib/demo/domain.ex
+ lib/demo/other_domain/
+ lib/demo/other_domain.ex
The rules around domain are
- Only
domain.ex
and siblings calldomain/*
.domain/*
don’t callother_domain/*
domain.ex
can exposedomain/*
.domain/*
can use all../*
(siblings ofdomain.ex
).
Entities
One thing I didn’t like about Context is that it gets bloated so fast with all the trivial queries and repo calls from its many schemas. So I have them distributed to their schemas and call the module just “entity”. If queries get complex, they will need a dedicated module.
- Demo.Context.get_this/1
+ Demo.Domain.This.get/1
- Demo.Context.get_that/1
+ Demo.Domain.That.get/1
# domain.ex
alias __MODULE__.{This, That}
The rest
I didn’t wanted shallow, insignificant directories oozing out to the project root. So I put them in priv/
. I think it makes sense categorically. With test/
dissolved in lib/
, it was just assets/
and rel/
.
- assets/
+ priv/assets/
- rel/
+ priv/rel/
With the structure, I’ve put "app"
when I need a general naming. application.ex
became app.ex
with app/
. And config/config.exs
became config/app.exs
to avoid meaningless repetition in path.
- config/config.exs
+ config/app.exs
- lib/demo/application.ex
+ lib/demo/app/
+ lib/demo/app.ex
This is something else, but I changed phx.gen.auth
into an email-only authenticatIon process. It’s a much lighter auth solution that will cover many use cases.
The top module
lib/*.ex
inherits lib/*_web.ex
and continue to define interfaces for other modules. I’ve renamed them and removed unused interfaces according to the new scheme.
Demo
.domain/0 # controller/0
.entity/0
.live/0 # live_view/0
.user_interface/0 # html_helper/0, I find `html` too narrow.
.routes/0 # verified_routes/0, the word already means established paths between two points
static_paths/0
is moved to config/app.exs
and refered in Demo.App.static_paths/0
.
Project structure example
Anything in straight line is sibling, diagonal is child.
demo/
config/
app.exs dev.exs prod.exs runtime.exs test.exs
lib/
demo/ demo.ex
app/ app.ex
components.ex home.live.ex layouts.ex
error.ex error.test.exs
auth/ auth.ex auth.test.exs
access.ex access.test.exs
account.ex account.live.ex account.test.exs
endpoint.ex
gettext.ex
mailer.ex
repo.ex
router.ex
telemetry.ex
test_helper.exs
priv/
assets/ gettext/ rel/ repo/ static/
Now, teach me.