Discussion about domain-orientated folder structures in Phoenix

phoenix
discussion
ddd

#1

Continuing the discussion from Domain-oriented web folder structure:

Essentially:

hello_web
├── controllers
│   ├── foo_controller.ex
│   ├── bar_controller.ex
├── templates
│   ├── foo
│   │   ├── index.html.eex
│   │   ├── ...
│   ├── bar
│   │   ├── index.html.eex
│   │   ├── ...
├── views
│   ├── foo_view.ex
│   ├── bar_view.ex

to:

hello_web
├── foo
│   ├── foo_controller.ex
│   ├── foo_view.ex
│   ├── templates
│   │   ├── index.html.eex
│   │   ├── ...
├── bar
│   ├── bar_controller.ex
│   ├── bar_view.ex
│   ├── templates
│   │   ├── index.html.eex
│   │   ├── ...

I hope @chrismccord will consider making this easier in a future release :slight_smile:


Domain-oriented web folder structure
#2

It can’t get any simpler than:

use Phoenix.View, root: "lib/hello_web/user/templates", path: "*"

So I don’t think there’s any more we can provide here. It’s already possible using the same setup that we provide out of the box :slight_smile:


#3

No biggie, but there’s a case to be made that the folder structure @ khaledh suggests is more in accordance with DDD. Personally, I’ve always perferred this feature based division, DDD or not.

A beginner would have to dig a little bit to achieve this. For it to be default is probably wistful thinking. :slight_smile: But it would be nice to e.g. have it as an option to phx.new


#4

An option to phx.new would provide two ways of doing the exact same thing, which actually could harm newcomers with an unnecessary choice. Our goals with Phoenix defaults have always been sane defaults that are easily customized for those that require it. Wrt to DDD, we treat the the web as the domain. Grouping templates, views, and controllers is file organization, which the proposed alternative is just fine, but not something the Phoenix team views as preferable. But the point is there is nothing special about our defaults, and to achieve your own desires you can use the same options we use to ship our own out-of-the-box behaviour.


#5

Fair enough. I see your point about not wanting to add an option that is a variation over the same.


#6

Hey @chrismccord, it’s great to hear your thoughts on this issue :slight_smile:

I feel I’m missing something here. Where would this code go? I’m assuming it would replace the default in HelloWeb:

use Phoenix.View, root: "lib/hello_web/templates", namespace: HelloWeb

If that’s the case then wouldn’t this just move the template root to another folder for all views? My goal is to customize the template folder per view.

Another way to interpret your suggestion is to add that line inside each view, i.e.

defmodule HelloWeb.UserView do
  use HelloWeb, :view
  use Phoenix.View, root: "lib/hello_web/user/templates", path: "*"
end

If that’s the case, then I think we’d have to remove the call to use Phoenix.View in the HelloWeb module, otherwise it would be called twice.

Wrt to DDD, we treat the the web as the domain.

Fair enough. I can see this being easier to grasp for most people starting with Phoenix. It’s just for us who got accustomed to organizing code by domain it wasn’t so obvious, hence my question :slight_smile:


#7

Correct, you could either use @tme_317’s example and keep the code inside the view function of AppWeb, or you could remove that line and call use Phoenix.View, ... as in each view.


#8

Great! Thanks again for chiming in :slight_smile:


#9

For any application using Phoenix, the web is a delivery mechanism and not a Domain.
i.e. a todolist app might have two domains Tasks & UserManagement, they are both available over the web but there is no web domain.

Within Phoenix the web can be considered a domain but that would be an argument for how the Phoenix project might order its code and should not be a concern of the end user.

I made some similar comments in my recent talk on the MVC architecture. This is the resource I use to explain my reasoning is https://en.wikipedia.org/wiki/Cohesion_(computer_science)
Grouping things because they are “controllers” is Logical cohesion and according to that page that is the second “worst” type of cohesion.


#10

I think we actually agree on this point:

Phoenix is indeed only the web interface to your application. The Tasks and UserManagement contexts would live outside of app_web/.

This would be true if we encouraged users to write their application and web code together, such as app/user_management/controller.ex|user_management.ex, but this would directly couple app code and web code together. If we agree the Phoenix is the delivery mechanism to your application, then it absolutely makes sense to group controllers together, because we are only concerned with dispatching web requests into our application, and converting the results into responses.

For example, if we look at the web directory structure of such an app:

app_web
  controllers
    user_controller.ex
    task_controller.ex
    api
      v1
        task_controller.ex
    resolvers
      task_resolver.ex

It passes the test of screaming its intent, within the context of The Web. We are translating user and tasks requests into responses. Those controllers may hook into entirely decoupled parts of our application. With a glance, I can immediately see what the web layer is responsible for, down to handling user requests, a JSON an API, and servicing graphql clients for tasks. If the app had grouped the directories per user/task, I would need to hunt down what the web story actually looks like.

So for me our defaults encourage better application design exactly because the web side is only focused on being the delivery mechanism. Of course we don’t force this on folks and you are free to structure your directories as you see fit :slight_smile:

Hopefully that gives insight to our dir structure.


#11

A feature based folder structure simply divides the web context into smaller sub-contexts, still within the context of the web/presentation. The web part is still just as much focused on being the delivery mechanism. I fail to see how a folder structure like e.g. the one below is less clear than the existing one:

app_web
  task 
    task_controller.ex
    task_channel.ex
    templates
      edit.html.eex
      index.html.eex
      ...

Rails popularized the MVC architecture — but Rail’s version is in many ways a misreading/reconceptualization of the original proposal [1, 2]. It works completely fine, but I don’t think it’s a given it’s best way to do it; I think it’s more a question of what one is most familiar with.

I think of it as slicing things horizontally vs. vertically. Rails slices things into horizontal layers, but an argument could also be made for vertical slicing [3].

Django is one example of an alternative solution, .NET’s Razor Pages is another novel and simple way of doing it.

  1. http://stephenwalther.com/archive/2008/08/24/the-evolution-of-mvc
  2. http://heim.ifi.uio.no/~trygver/2007/MVC_Originals.pdf
  3. https://www.kenneth-truyers.net/2016/02/02/vertical-slices-in-asp-net-mvc/

#12

To elaborate:

MVC has been subject to re-interpretation over the years - almost to the point of meaninglessness. The web model (Server MVC) goes back to the JSP Model 2 Archtecture (1998) - and really only has a vague resemblance to the pattern outlined in 1979 by Trygve Reenskaug.

model2

MVC: misunderstood for 37 years
MVC past, present and future.
MVC Tree

Whats a Controller Anyway?

POSA1 gets into Trygve’s MVC and describes the distribution of responsibilities as :

View:

  • Creates and initializes it’s associated controller (***)
  • Displays information to the user
  • Implements the update procedure (***)
  • Retrieves data from the model (***)

Model

  • Provides functional core of the application
  • Registers dependent views and controllers
  • Notifies dependent components about data changes

Controller

  • Accepts user inputs as events
  • Translates events to service requests for the view
  • Implements the update procedure, if required

In Server MVC the View looses a lot of responsibilities (***) - i.e. it is comparatively anemic leading typically to a Page Controller.

Interestingly POSA1 also identifies the Document-View pattern as the result of combining the View and Controller into a single component. And as a matter of fact:

Objects and the Web. Alan Knight, Naci Dai (2002)

POSA1 further lists under MVC liabilities:

Point: in many situations the controller and view are so closely related they may as well be considered belonging to the same component.

Counterpoint: What about an implementation style where most pages are composed of fragment markup rather than all-in-one page markup, most likely because you are reusing “widget skins”? In OO this would be approached with Presentation-Abstraction-Control (PAC)/Hierarchical MVC (HMVC) but in Server MVC you tend to be stuck with controllers that operate at page granularity - which doesn’t match the granularity of your fragment markup.

So depending on the implementation style one can argue both ways - and once again we arrive at the universally dreaded “it depends” (on the details).

As far as I can tell the default organization under (?_web) inside a Phoenix project is favouring making reuse of fragment markup between page controllers as easy as possible (whether or not there are significant opportunities for reuse (or legitimate DRY)).


#14

I think this is a very good argument!

The question is is it better to have e.g. UserController and TaskController in the same folder, or would it be better to have UserController, UserView, and corresponding templates in the same folder? I currently can’t see much benefits of the former approach, and I can confirm that I’m occasionally annoyed by the current folder structure when working on the web part. I frequently find I need to navigate between separate folders, which is a bit distracting.

In my experience, most often I need to make changes to all the three aspects of a single entity (e.g. a User), so it seems that grouping per entity would work better for the most cases I’ve seen.

Personally, I’ll think about trying out the alternative approach in my next project.


#15

Will be keen to hear your thoughts.
I have a project set up like this e.g.

www
- sign_up.ex
- sign_up.html.eex

The .ex file contains a module that handles both the GET and POST action, so serving the sign up page and the action of signing up are co located. It has worked very well for us and allowed us to discover larger bounded contexts as needed


#18

Hi everyone,

As a beginner trying to figure things out, I’m also a bit puzzled by this topic. I come from Django and one of the nice things about it is that it allows to divide a project into apps that can be plugged into different projects. That allows developers to build projects by combining existing packaged apps along with their own apps. Each app bundles a data access layer (models), a web request handling layer (views) and templates. It’s a quite practical approach that has allowed a thriving ecosystem of reusable apps. I’m wondering how we could achieve similar re-usability of web projects “chunks” in Phoenix. Clearly in Django the goal of isolating the web from domain logic isn’t as strong as in Phoenix though. Because of my background, this is a concern that still seems a bit theoretical to me. I’m not judging because I’m still trying to figure things out (and certainly don’t want to add more negativity to this thread). I just thought my perspective might bring something to the debate since I understand the majority of Phoenix users tend to come from Rails.


#19

Welcome!

I have a limited experience with Django (so a lot of what I’ll say is probably wrong) but I think it tracks with what you’re describing. Django tends to create vertical slices of functionality with its apps. That means that you can get something out of the box that encompasses the entire functionality from views to the database. Rails supported something similar in what they called engines and some libraries took advantage of them. Some people loved them but my impression around the community was that people generally didn’t use them other then by happenstance in the libraries they pulled in. The trend was for people to divide much more functionality “horizontally”. I think that trend continues in Phoenix and elixir and finding the right way to further divide those horizontals is a lot of what this conversation is about from what I can tell.

While there are a bunch of different ways to create re-usable bits in elixir, if you’re talking about re-usability in phoenix specifically than its important to understand that despite providing configuration for a lot of things, phoenix itself is a way to handle requests (with plug underneath), a way to render views and channels. So if you want to provide functionality for phoenix one of the easiest ways is to provide Plugs that can be added into your existing pipelines. If you want to provide a larger vertical slice of functionality then it requires more manual steps for the end user. ExAdmin might be a good example of that: https://github.com/smpallen99/ex_admin


#20

Given the diversity of opinion in this thread, I don’t think there will be one right way of organizing code in a web context. Preference and background (django, rails, ddd, etc) will play a role in how people organize their code.

My understanding is that the philosophy of Elixir and Phoenix is to build a small core that can be customized for various scenarios and use cases. While the default code organization of Phoenix is oriented towards how it separates concerns (controllers, views, templates, channels), it’s still possible to transpose it to be domain-oriented. However, there a couple of issues that I found doing this: a) it’s not quite obvious how to do it, and b) the changes required felt more like a work-around rather than a supported, well-documented change.

I still think that a domain-oriented code organization, even in the web part of a project, is more favorable for a lot of users. The main reason is this:

In my experience, making a change usually involves touching multiple related files across the controller and templates of the same domain (and sometimes adding/changing a function in the view). But with Phoenix’s default organization I have to keep multiple folders open in my editor and it’s quite distracting to find the files I need to change, especially when template file names look identical in most cases (index.eex, edit.eex, show.eex, etc). With domain-oriented folders I can expand one folder and make all the changes I need there.

To draw an analogy, imagine I’m also building a CLI for my app, and I’m using a CLI framework that by default organizes code like this:

lib
├── app
│   ├── users
│   │   └── ...
│   └── tasks
│       └── ...
│
└── app_cli
	├── commands
	│   ├── user_command.ex
	│   └── task_command.ex
	├── args
	│   ├── user
	│   │   ├── create.ex
	│   │   ├── list.ex
	│   │   ├── show.ex
	│   │   └── update.ex
	│   └── task
	│       ├── create.ex
	│       ├── list.ex
	│       ├── show.ex
	│       └── update.ex
	└── formatters
		├── user_formatter.ex
		└── task_formatter.ex

This is of course hypothetical, but it’s not far-fetched. Even though the CLI framework knows where to find things in a more predictable way, it made my life harder by making me spend more effort when I need to change the command/args/formatter in a single domain. I’d organize the CLI code like this instead:

 lib
├── app
│   ├── users
│   │   └── ...
│   └── tasks
│       └── ...
│
└── app_cli
	├── users
	│   ├── user_command.ex
	│   ├── user_formatter.ex
	│   └── args
	│       ├── create.ex
	│       ├── list.ex
	│       ├── show.ex
	│       └── update.ex
	└── tasks
		├── task_command.ex
		├── task_formatter.ex
		└── args
			├── create.ex
			├── list.ex
			├── show.ex
			└── update.ex

Again, that’s just how I’d organize my code. I can expand one folder and focus on it when I’m making changes. Other domains can remain collapsed, keeping distraction low.


#21

I usually find the arguments for co-locating controllers/views/templates quite intriging, but at least for html templates I often don‘t work with a 1:1 mapping of controllers to views, but I have multiple controllers using some sometimes shared view modules, which would be harder to do if files are supposed to be in the same folder. I can see that being different for plain json api projects, though.