goncalotomas

goncalotomas

Cleanly separating Verified Routes using Boundary

I’m working on a Phoenix LiveView app that uses boundary, and we’ve bumped into warnings writing out a way to do notifications. There are a couple of tricks that would remove these warnings, but I care about the right way to model data and to organize the code. :slight_smile:

Here’s a rough example of the code I’m working with:

defmodule MyApp.Notifications do
  @moduledoc """
  The Notifications module is responsible for managing user notifications - both in-app and email.
  """
  use Boundary, top_level?: true
  # importing verified routes 
  use MyAppWeb, :verified_routes

  def send_invitation(invitation) do
    to =
      if invitation.user_id do
        url(~p"/app/users/invitations")
      else
        url(~p"/auth/register"))
      end

    MyApp.Mailer.deliver_invitation_email(invitation, to)
    send_invitation_notification(invitation)

    # ...
    :ok
  end

  defp send_notification(invitation) do
    # ...
  end
end

The real code is a bit more complicated, but hopefully this illustrates the issue.

We’ve configured Boundary to reject any calls to from the MyApp context to the MyAppWeb context, which makes this module not pass the boundary checks:

warning: forbidden reference to MyAppWeb
  (references from MyApp.Notifications to MyAppWeb are not allowed)
  lib/my_app/notifications.ex:17

It makes sense to prevent access to the Web context from the non-Web, but in this case we’re, were just trying to use verified routes so that the links for the invitations are verified to exist. Previously we could use Phoenix.Router.Helpers, but from the docs, it looks like the routes module helper is deprecated, so no help there.

Moving the logic that switches between the possible URLs inside the Mailer doesn’t seem helpful, since it looks like we’re leaking the logic around Invitations when Mailer should just care about sending email.

We’ve also toyed with a few ideas that basically revolve around replacing the Route Helpers module, but quickly ran into the thought of “there’s got to be a better way to do this”.

Can someone help us organize our code a bit better?

Thanks! :smiley:

Most Liked

LostKobrakai

LostKobrakai

You can move what use MyAppWeb, :verified_routes does to a separate module, which you can independently scope with boundary.

You could also reverse the dependency by injecting the module for url generation e.g. using config. Works around Boundary a bit, but imo is the architectually cleaner approach.

goncalotomas

goncalotomas

There was a new talk from Saša recently published that talks about the process of both incorporating boundary into a large new codebase and also working towards removing projects from umbrella format:

I found it a great talk, and there’s no better source than the author about this library :grin:

al2o3cr

al2o3cr

I’ve used the “module in config” approach to solve this exact issue in an umbrella app.

It has a couple components:

  • a module in MyAppWeb that exposes a URL-generation API
  • code in MyApp that looks up a module and then calls functions on it
  • config to set the key for MyApp to the module from MyAppWeb
defmodule MyAppWeb.NotificationUrls
  def for_invitation(invitation) do
    if invitation.user_id do
      url(~p"/app/users/#{invitation.user_id}/invitations")
    else
      url(~p"/auth/register")
    end
  end
end
defmodule MyApp.Notifications do
  ...
  def send_invitation(invitation) do
    to = url_module().for_invitation(invitation)

    ...
  end

  defp url_module do
    Application.fetch_env!(:my_app, :notification_urls)
  end
config :my_app, notification_urls: MyAppWeb.NotificationUrls

One downside of this simple approach is that the dynamic apply is very hard for most tools to “see through”, so you probably won’t get compile-time warnings if something’s incorrect (calling for_invitation with the wrong arity, etc)

You can mitigate most of that with some additional boilerplate-y code:

  • extract the Application.fetch_env! part and the call into a separate module in MyApp
  • create a @behaviour that it implements
  • also implement the behaviour in MyAppWeb.NotificationUrls

Where Next?

Popular in Questions Top

itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: https://hexdocs.pm/ecto/Ecto.Schema.html#module-...
New
_russellb
I want to try my hand at web scraping. What tools/libraries do I need to use. I’m hoping to turn this into something professional so don’...
New
tduccuong
Hi, is there any work on GUI with Elixir, that is similar to Electron/Javascript? My idea is to bundle Phoenix and BEAM into a single se...
New
sacepums
Hey guys. I'm new to elixir and im really stocked about it. But I ran into a bit of problem - I need to convert a date sting, for examp...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
LegitStack
I’m trying to make a websocket server in Phoenix or raw Elixir. I heard about gun, I think I could use cowboy, but since I’m not that sma...
New
beno
I will often find my self writing things similar to: case some_value do nil -> something() "" -> something() _ -> someth...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New

Other popular topics Top

sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
William
I would like to know that is there any online source for learning Phoenix Framework for building E-Commerce Store? Any advantage on build...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

We're in Beta

About us Mission Statement