One reason for this separation is that Elixir as a language is compiled and not interpreted. Compiled languages often feature build tools that also handle dependency/package management. But because both Ruby and Python are interpreted languages aka don’t need to be compiled, dependency management can be handled at the framework level.
In practice, you can see this play out when creating a new project.
mix new app # create a new Elixir app via the language build tool
mix phx.new app # create a new Elixir app with Phoenix templates
# ^ also notice how it's not something like `phx new app`
rails new app
django-admin startproject app
Your article leads me to believe you want contexts. When the context outgrows your phoenix application just create a new mix project, move the relevant code over, and have your phoenix app depend on your new mix project.
There isn’t really a 1-to-1 replacement for django “apps” in the Elixir Phoenix world.
Django does a lot of work under the hood in an opinionated way, so you install a library, add “some-app” to the list of installed_apps, perhaps add some configuration, perhaps add something to the urls.py and that will magically give you endpoints, database tables etc.
In the Phoenix there isn’t such tight coupling between the different layers (eg web server, database, web framework etc) That often means you have to wire things up a bit more - perhaps manually create some database tables, implement a controller or two, write some callback modules required for the library to hook into. The benefit of that, though, is that you have much more control over those different layers which is really handy as applications grow and requirements change.
I see one could use macros to inject routes, and an install mix task is a common approach AFAICT.
I get one want control, but I also believe not having an official guide (with simple examples) about how to develop reusable “contexts” is still a missed opportunity.
We could end up with a pletora of reusable libs that could make the whole ecosystem grow (as it was the case for Django). Just my 2c.
I believe Elixir School is the closest thing to what you’re looking for, but it currently lacks a guide for how/why to create libraries. I agree it should have one. Elixir docs have guidelines but they are not really aimed at a beginner and are more low level.
I’m not sure a Phoenix specific guide would make as much sense. Phoenix routing is a pretty thin wrapper around Plug, which is itself a separate library. In general Elixir libraries tend to be more modular, sticking to the basic problems as much as possible and allowing developers to solve higher level problems in their own way, according to their own needs (which tend to change over the lifetime of an application).
It’s frequently observed that one thing that really sets Phoenix apart from the other major web frameworks is the way it strives not to impose an overly specific set of constraints on your application in such a way that it suddenly makes a lot more sense to describe it as a Phoenix application vs an Elixir application. This has been summarized in the phrase “Phoenix is not your application” (not sure about its origins, maybe the talk referenced in this thread).
None of this is cut and dry obviously. Phoenix makes plenty of decisions, and handles lots of details for you, otherwise it wouldn’t be useful. You could say it’s about better separation of concerns. In my opinion it’s really about a more fundamental philosophical difference in the approach to software architecture. Elixir and most of its major libraries trade “magic” for explicitness, “ease” for simplicity, “flexibility” for reliability (and I am sincere in describing those as tradeoffs). To take a contrasting example, Rails wanted to do as much as possible for the developer, and largely intentionally, lock the developer into a certain way of doing things (the so-called “Rails Way”). I have less experience with Python and basically none with Django, so I don’t know how things stand there, but Rails apps tended to be very speedy to spin up but very problematic to maintain over the long haul.
Anyway, I think this all means that there is not really a single “way” to use Phoenix that could inform such a guide, because that would imply that Phoenix had created some specific ways to extend itself that sets it apart from other Elixir libraries, and it goes out of its way not to have to do that. Macros are the way to write code meant to be reused by other developers. Phoenix uses macros to extend the libraries it itself uses (like Plug) and other libraries use macros to extend Phoenix.
Routing is just a single piece of what I consider a pluggable “context”.
The other two are (optional) static assets and (optional) database tables.
The “context” configuration is described in the Library Guidelines in a very clear way.
I think those are even clearer examples of what I mean. Phoenix itself does very little to manage those for you. Mainly it generates some default Plug config. For FEs it does have some default integration with esbuild but that’s about it (although my focus has mainly been API only apps so I don’t have much experience there).
The “context” configuration is described in the Library Guidelines in a very clear way.
Puzzled by what you mean exactly by this. “Context” has a specific meaning in Phoenix, basically just "the place where your business logic goes, but they are just plain modules. There are certainly some domains that are common between applications, for example user account management, and of course there are loads of libraries around that, for authorization, oauth, etc, but even they don’t ship “contexts” to be used. They have their own code organization specific to their internal app logic and provide an API (often built with macros) for adding to your contexts, but remaining completely separate from Phoenix (even when depending on various Phoenix APIs).
Sorry if this comes off as overly pedantic or something. I am really just trying to get clarity into your question/suggestions. And usually I think this goes without saying, but to be clear these are just my own (albeit fairly strong) opinions about developing with Phoenix. Other devs might reasonably differ of course–including the Phoenix authors themselves (and in that case I highly recommend you listen to them )
@tfwright let me be clear: I am not arguing that one between Django and Phoenix is better than the other, that’s completely OT.
And “contexts” was just bad wording, sorry.
Let me elaborate on my initial request with an example.
Key here is reusable, because I want to use LibA as a dependency in the 10 Phoenix projects I am currently working on.
Based on the previous answers, my option to develop LibA to be reusable is to go and find similar libraries which are open source (for example, phoenix_live_dashboard), and see how they made it.
What I believe could be useful would be to have another option:
going to the official guide about writing reusable libs, where I can find out techniques to develop libraries that can be easily included into Phoenix projects.
I believe it could make life easier for developers, but I got why you believe it’s not so important.
The introduction post for Phoenix’s authentication solution is worth a read – it dives into the rationale behind why its added via a mix task i.e. mix phx.gen.auth that injects code rather than a separate library that would hide code.
With time, I realized that what I want from an authentication system is for it to be as straight-forward as possible. When considering an authentication system for a server-side MVC application, I don’t want to hide my model/domain code under a framework/library.
When it comes to controllers, views, and templates, they belong directly in my web application, as I may want to customize the user interface and the user experience.
Therefore, with all things considered, there is very little space for an authentication framework. So what does it mean? Everyone has to write their authentication system from scratch?
Not really. My proposed solution is to provide generators to inject all relevant authentication code into your application.
I am not making that argument either, just comparing differences. On the other hand, to the extent the differences are real, that means developing in the Elixir ecosystem might be more or less enjoyable than Django (or Rails, or whatever) depending on your personal preferences when developing for the web. As you said, it’s about tradeoffs.
This is what I meant by a “philosophical” difference and why I spent so many words trying to answer something that otherwise might seem like a simple question (“X exists in Django, where is X for Elixir?”). Certainly not to get into an argument about which one is “objectively superior.” There are really many possible gradations between “directly in my web application” and “hid[den in] my model/domain code under a framework/library” and thus there really is no single approach. (However, now that I am typing this, I do actually think a guide that covered these options and highlighted José’s POV on the matter might be a great addition to the Elixir School.)
Rails had a concept of “engines” (not sure if it still does, my impression is that they were losing favor) where simply by installing a library an entire slice of your web application would be made available instantly, including routes, controllers, business logic, etc. Not sure if Devise ever made use of that, but the code José shared was actually a less “extrusive” option: a helper that injects code into preexisting objects by metaprogramming, pretty much a macro. And finally, Rails of course also had generators that literally edited source code. Point being, Rails had all those options, and there’s no reason Phoenix couldn’t as well, and in fact many libraries do choose between generators and a macro-based API for integration strategies.
However, and to return again to the reference for the original question, I don’t believe there is anything comparable to the engine system, which I take to be the closest thing to what you’re thinking of, and generally the commonly accepted best practice is to avoid even macros until they are “necessary,” but I would not go so far as to say generators are always better. It really depends on the domain. José is discussing authentication specifically, and the quote invokes the need for extensive customization, and the argument makes the most sense in that context. Whereas Phoenix LiveDashboard uses a macro that is more like the Rails Devise API insofar as it injects various routes and controllers which remain “hidden” away. The key difference being that there is little expectation that they would need to be customized, and any customizations would be minor. At least, I assume that was the reason for the choice there.
So the Elixir School Elixir website has this article on macros which I do think is helpful. What follows is the intro that sort of sums up what I’ve been saying in this thread, but I absolutely think it could be expanded on a lot and put specifically into the context of “So you are ready to write your first Elixir library” or something, and made into an article on its own preceding the nitty gritty details of dealing with the macros themselves.
Even though Elixir attempts its best to provide a safe environment for macros, the major responsibility of writing clean code with macros falls on developers. Macros are harder to write than ordinary Elixir functions and it’s considered to be bad style to use them when they’re not necessary. So write macros responsibly.
Elixir already provides mechanisms to write your everyday code in a simple and readable fashion by using its data structures and functions. Macros should only be used as a last resort. Remember that explicit is better than implicit. Clear code is better than concise code.
taking a quick stab at how such an article would be structured:
Here are x design questions to consider specifically when writing a library, with y tradeoffs
Generally the best practice is not to hide code, i.e. to favor imports over macros, and generators when customization is expected
…But everything depends on the domain and here are some good examples from various domains: [Phoenix auth generator, LiveDashboard, others maybe]