Phoenix Context connection struct best practice

I understand that the a Phoenix controller is the web interface into the bigger application, and the controllers call functions in context. Does this mean that it’s not a best practice to pass the connection struct, which is related to web interface to the contexts?

I have an endpoint that passes through a plug which checks for authentication and assigns an organization, user and token struct to the connection struct. Now, I can explicitly pass all these three values to the context or I can pass the connection struct itself. Which is better?

Thanks

3 Likes

I would love to hear other opinions on this, but I think the key thing here would be that the contexts do not specifically know about %Plug.Conn.

You definitely don’t want this context function signature…

def create(%Plug.Conn{} = conn)

You could be less specific about it and grab things out of a struct that just happens to have the same structure as a conn if it really makes sense for your context API, but it almost surely does not…

def create(%{assigns: %{organization: organization, user: user, token: token}})

However, this is coupled to the structure of a conn and doesn’t seem like clean arguments for the context function.

You could pass just the assigns so as not to reach deeply into the struct…

def create(%{organization: organization, user: user, token: token})

But you are still reliant on how the conn is built up on the web side of your app and it is still arguable whether this is a clean way to take arguments in the context function.

Try to imagine you didn’t have a web interface for your app at all and were only designing the context function. How would you want the function signature to operate? Let that dictate what/how arguments are passed in.

For instance, maybe a function signature like this makes more sense in your app…

def create(%Organization{} = organization, %User{} = user, token})

This should be more resistant to breakage due to changes in the controller because you will have been explicit of the needs of the function at the boundaries.

7 Likes

I also avoid passing Web conn to the core.You might want to change for a Text interface later…

3 Likes

Plugs and controllers are specifically there to convert the input (being the http request) to input for functions on contexts (now no longer a http construct, but elixir values). Just like a CLI would convert argv input (usually just string values) to elixir values before querying the contexts.

5 Likes

Really depends what you are after but in any case I am with @baldwindavid that you shouldn’t leak Plug.Conn to your contexts. If you need parts of your assigns / session / private conn attributes then just pass them to your context functions. I’d even advise you to trim absolutely everything from them that you don’t need directly.

If you only need :cart_id and :user_id for a given context function then I’d do this:

defmodule MyApp.WhateverController do
  def index(conn, params) do
   conn.assigns
   |> Enum.take([:cart_id, :user_id]) # Only take the pieces of info your context function needs.
   |> MyApp.Orders.do_something_with_cart_and_user() # Pipe them to the context function. 
  end
end

As an example pseudo-code.

Absolutely don’t rely on implementation details. Now you have a Plug.Conn but later you might work with Absinthe (for GraphQL endpoints) and who knows what else.

4 Likes

Thank you for the great explanation!