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

stjefim
Hello! Suppose you are building workflow (order / task / payment) processing system with the following requirements: Each workflow con...
New
water
I’m aware of 2 such projects, are there others? git: GitHub - team-alembic/realworld: A fullstack Phoenix LiveView application with back...
New
itzmidinesh
Hi all, I’m working on a Phoenix project where I need to make external HTTP requests and I’m trying to choose between Req and HTTPoison....
New
peck
Hi everyone. My team and I have been working on a fairly modest app based around video streaming and chat, but we’ve landed a customer t...
New
leoschweizer
Hi, we run different LLM models across a cluster, with each node loading a configurable subset. Model-specific queues let nodes consume ...
New
csmr
Hi all, I’m new here and would like to hear peoples views on my Elixir/Phoenix based game-engine! :smiley: Mutonex gamedesign was finish...
New
perzanko
Do we have maintainer of absinthe-graphql/dataloader here? We have a PR that we’d like to merge to the library. Relatively small additive...
New

Other Trending Topics Top

dominicletz
Hey there, I haven’t seen a list of useful Elixir specific skills for the popular AI tools yet so I’m starting one here with my first. ...
New
mudasobwa
I fully migrated to my own harness from Anthropic/Gemini and I think it’s time to share it. Welcome DSH, the DeepSeek Harness, fully writ...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
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
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New
OndrejValenta
(I just needed to vent somewhere and LinkedIn is full of hope, or hype, I’m not sure which exactly) AI dream has many faces, but general...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews