matiso

matiso

Today I watched @chrismccord’s talk about the changes coming to Phoenix starting with version 1.3 and I really, really love the direction that Phoenix is going.

I have worked with both monoliths and microservices, and both have their known pros and cons. Phoenix Umbrella seems to be going in the right direction of providing best of both worlds: a strongly modular system, without the overhead of separate deployment.

One thing that I like about the --umbrella flag is that it completely separates the business layer from the web / presentation. But here I’d like to go even further. I would love to see an even more radical separation. Let me explain.

I have a web application that you can think of as a SaaS offering. It has a front page, pricing page, a login / signup form. After logging in, you get to see a dashboard that is a JS-app that communicates with a JSON/REST api. The REST API is the only source of data for the application and is both used by customers and the application itself.

Having this, I have created a Phoenix umbrella application structured the following way:

apps/my_app → the business logic layer (encapsulates and abstracts the communication with the db)
apps/my_app_web → the presentation layer for the HTML stuff
apps/my_app_api → the API business logic layer (mostly communicates with my_app, exposes the models)
apps/my_app_api_web → the API presentation layer for the JSON stuff

So the dependency tree looks like this:

my_app ← my_app_web (renders the assets)
my_app ← my_app_api ← my_app_api_web (exposes the REST api)
my_app_api ← my_app_web (uses the REST api)

The only way to achieve this structure was by creating the my_app umbrella project first, then the my_app_api umbrella second, and moving both my_app_api and my_app_api_web under the /apps folder of the my_app umbrella. I had to change the port of my_app_api_web to not collide with my_app_web. Not very clean, but hey, it works!

I’d love to see Phoenix evolving into a framework that naturally supports this kind of separation of concerns. Going further, I’d like to allow to have multiple “web” layers (e.g. public front-page vs application for logged in users, api, etc). This way the routers always stay clean and focused. The system naturally avoids growing into a bloated monolith.

Having a separate application for the API exposers might sound like an overkill, but I want to keep the business logic concerned with abstracting the DB in a clean way, and have the API layer be built on top of it. As an example, I have a lot of Swagger definitions in the API application, which I think does not strictly belong to the business logic.

Thanks again for making it possible, I’ve been waiting for this for so long!

Showing Posts 1 to 10

chrismccord

chrismccord

Creator of Phoenix

Phoenix 1.3 also includes two new project generators for running inside an umbrella’s apps/ directory: mix phx.new.web and mix phx.new.ecto. So in your example, you could use the --umbrella flag, then generate your api, api_web, and other apps with these two generates. I think we already support what you suggested :slight_smile: Glad you like the direction!

matiso

matiso OP

Wow, this is great stuff! Keep up the good job :smiley:

und0ck3d

und0ck3d

@chrismccord is it possible to run, let’s say “app_web” and “app_api” in the same port? Perhaps using a proxy? How can one achieve this, any example?

Thanks in advance.

und0ck3d

und0ck3d

I stumbled on this github repo which has an example of what I’m trying to achieve, using the master_proxy app.

Is that example suitable for development? I mean will it have any negative impact, such as loss of live reloading or anything else?

Because I’d have to run it like: (right?)

cd apps/master_proxy && MIX_ENV=dev mix run --no-halt`

Thanks once more!

Gazler

Gazler

Phoenix Core Team

I wrote the master_proxy application originally to solve an issue with running an umbrella application on Heroku, where only one port is available.

What you have suggested would work, however I find it much easier to run mix phoenix.server from the route of the umbrella and have both applications running on different ports. So I can go to http://localhost:4000 for the API, and http://localhost:5000 for the web application.

und0ck3d

und0ck3d

Thanks for the quick answer @Gazler!

In that case, to separate web and API, I think you’re right, and it’s more practical as it will probably run in a sub-domain (each app proxied by NGINX).

But imagine I have, let’s say, the web (application per se) and admin, which I’d like to separate in code, but to run on same port.

I’ve achieved this behavior by creating a mix phx.new myapp_admin --module MyApp.Admin application in /apps directory, removing the mod param from applications function in myapp_admin/mix.exs, adding this as a dependency of myapp_web and then forward "/admin" MyApp.Admin.Router.

However with this “solution” I’ve lost live reloading for the myapp_admin code and assets compilation…

Three questions, if you don’t mind:

  1. Do you think this is a good solution for this problem?
  2. How could I restore live reloading and assets compilation with this solution?
  3. For this specific case, are there better solutions?

Thank you in advance!

und0ck3d

und0ck3d

Well, I was able to make live reloading and assets compilation work again for the myapp_admin app, buy updating the following files in the myapp_web app:

File: myapp_web/config/dev.exs

...

config :myapp_web, MyApp.Web.Endpoint,
  watchers: [
    node: ["node_modules/brunch/bin/brunch", "watch", "--stdin", cd: Path.expand("../assets", __DIR__)], # watch myapp_web
    node: ["node_modules/brunch/bin/brunch", "watch", "--stdin", cd: Path.expand("../../myapp_admin/assets", __DIR__)] # watch myapp_admin
  ]

...

config :myapp_web, MyApp.Web.Endpoint,
  live_reload: [
    patterns: [
      # myapp_web
      ~r{priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$},
      ~r{priv/gettext/.*(po)$},                                                                                            
      ~r{lib/myapp_web/views/.*(ex)$},                                                                      
      ~r{lib/myapp_web/templates/.*(eex)$},

      # myapp_admin
      ~r{priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$},
      ~r{priv/gettext/.*(po)$},                                                                                            
      ~r{lib/myapp_admin/views/.*(ex)$},                                                                      
      ~r{lib/myapp_admin/templates/.*(eex)$},
    ]
  ]

File: myapp_web/lib/myapp_web/endpoint.ex

...

  # myapp_web
  plug Plug.Static,
    at: "/", from: :myapp_web, gzip:false,
    only: ~w(css fonts images js favicon.ico robots.txt)

  # myapp_admin
  plug Plug.Static,
    at: "/", from: :myapp_admin, gzip:false,
    only: ~w(css fonts images js favicon.ico robots.txt)

...

Ofc, this doesn’t look like a very elegant solution, but it works!!

I was wondering if there’s another way to work this out or improve this solution… Any ideas?

Thank you very much!

Gazler

Gazler

Phoenix Core Team

I can see why you would want them running on the same port in production (with some sort of proxy in front of them). I’m not sure why you would want to do it in development. If there is some reason that they need to exist together then they may not be good candidates for separating into different apps.

You an always run them both on separate ports to allow you to run mix phoenix.server from the root for the code reloading. Then you can forward as you have done already and just ignore the fact that the admin section is running on a different port. This means that you don’t need a work around like you have, but you can also run both applications in isolation.

If you look at the Phoenix supervisor, you will see that both the watcher_children (for code reloading) and server_children workers use the same server?/1 function to see if they should be started phoenix/lib/phoenix/endpoint/supervisor.ex at 4ff5a3e21ed8e1ca892e3bb60d3511d4cf8a2a1b · phoenixframework/phoenix · GitHub

und0ck3d

und0ck3d

All of this comes from the will to separate code as much as possible so that I don’t end up with a monolithic application. That’s why I want backoffice and frontoffice code to live in two separate “apps” or “directories”, as I would if I was to add an API to this project as well.

I now see that it really doesn’t matter if apps are running on different ports as long as I forward the app-sepcific path to correspondent router. I believe that approach will work well instead of the messy workaround I previously had in place.

However, when using that approach, as I’m calling the App.Backoffice.Router instead of calling it’s App.Backoffice.Endpoint, assets won’t be available if I’m accessing it through the forward. I mean, in development I’ll access localhost:4001 for developing the backoffice and I can access the assets. But in production it won’t be able to reach the assets, right? (at least this is what I’m experiencing right now)…

I don’t believe there is any reason that they need to exist together unless (I haven’t tried this yet) regarding authentication… Given this case where I separate both frontoffice and backoffice, given your experience, do you anticipate any issue with having authentication setup in both apps? As they’ll eventually have separated code for serialization and authentication it shouldn’t be a problem, right? But in the session-level do you think they could collide or something?

Sorry for all the questions! Thanks!

obsidienne

obsidienne

Hi,

Is it possible to add to Deploying on Heroku — Phoenix v1.8.8, a chapter about master proxy ?

My app has multiple phoenix app (scaffolded from phoenix 1.3 / different PORT) under the same umbrella and everything work in dev. And because I have to use heroku, a master_proxy is needed.

I have found using this forum the following gists: Websockets in Elixir with Cowboy and Plug · GitHub and Example MasterProxy for Elixir Umbrella applications supporting websockets · GitHub

But something more official/easy to find would be cool (one working with websocket).

Thanks

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews