Guides and best practices to create reusable apps and libs for Phoenix?

Hi all,

something I am missing (coming from Django) are guides and best practices to create reusable apps and libs for Phoenix.

Every library I use seems to have a different way to be installed.

I am wondering if this is because the nature of the language, or the framework, or is that just a lack of documentation? (not ranting!)

The documentation can seem a bit more scattered when coming from paradigms where the web framework “encompasses” the language e.g. Ruby on Rails and Python/Django.

The documentation for library guidelines and project compilation lives in the Elixir docs and the documentation for dependency management lives in the Mix docs.

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.

# Elixir
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 
rails new app

# Django
django-admin startproject app
1 Like

Thank you!

I was thinking more at guidelines to develop reusable apps specific to Phoenix though.

Just an example:

  • How can I create a reusable lib that adds routes to an existing Phoenix project?

Something similar to:
How to write reusable apps in Django

Cheers!

1 Like

In elixir/phoenix it has to be more verbose. You can look how GitHub - phoenixframework/phoenix_live_dashboard: Realtime dashboard with metrics, request logging, plus storage, OS and VM insights or GitHub - mojotech/torch: A rapid admin generator for Elixir & Phoenix does that. You shouldn’t magically inject routes to the router. You may have a look also how phoenix_gen_auth does this with an installer script.

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.

@dkuku Mentioned some good examples of Phoenix libraries which do this well - I’d add GitHub - sorentwo/oban: 💎 Robust job processing in Elixir, backed by modern PostgreSQL or SQLite3 and GitHub - ozziexsh/bling: 💰 Add recurring subscriptions to phoenix applications to that list.

3 Likes

Thank you, I will look at the projects.

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.

Cheers

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 was thinking at something like guidelines, but specific for Phoenix libraries.

So we can have one single source of truth for phoenix contrib developers.

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.

1 Like

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.

Thank you, that is fair and clear.

Cheers

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 :slight_smile: )

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

Let’s say I am developing a reusable library LibA that includes some components, a css file, a javascript file, and a couple of database tables.
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.

Hope I made my original question clearer.

Cheers!

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.

source: An upcoming authentication solution for Phoenix - Dashbit Blog

That’s very interesting, and forces me to make a mental switch as I come from the other side of tradeoffs.

Cheers!

2 Likes

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.

2 Likes

I feel like this is what I would like for my specific use case, where I have lots of Phoenix projects which really shares the exact same controllers, views and templates.

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.

edit:

taking a quick stab at how such an article would be structured:

  1. Here are x design questions to consider specifically when writing a library, with y tradeoffs
  2. Generally the best practice is not to hide code, i.e. to favor imports over macros, and generators when customization is expected
  3. …But everything depends on the domain and here are some good examples from various domains: [Phoenix auth generator, LiveDashboard, others maybe]
3 Likes