Should I separate context queries from special web queries?

I am contemplating whether I should separate highly specific queries which only a single controller uses from the more general purpose queries located in the context module. Let me set the picture first:

Let’s say I have a Phoenix project with the following simplified project structure:

lib/
  my_app/
    post_context.ex
  my_app_web/
    post_controller.ex

The PostContext holds the typical general purpose queries like list_posts/0 get_post!/1 update_post/2 and delete_post/1.

The PostController however uses a highly specific query, which is written only for this controller. Something like:

def get_latest_posts_by_top_authors_ranked_by_likes(top_author_ids) do
  from(p in Post, 
    where: p.author_id in ^top_author_ids
      and not p.deleted?
      and not p.under_review?
      # only show posts from this year
      and p.published_at > ^~N[2021-01-01],
    order_by: [desc: p.likes]
  )
  |> Repo.all()
end

My point is that this query solves a very specific need by the controller and isn’t used anywhere else. Usually, I would still put this query in the PostContext, because that is where you usually put queries. However, I thought since this query is only used by a single controller and nowhere else, why not put it into the my_app_web folder? This would be the new project structure:

lib/
  my_app/
    post_context.ex
  my_app_web/
    post_controller.ex
    queries/
      get_latest_posts_by_top_authors_ranked_by_likes.ex

What do you think about the new project structure?
Do you think it unacceptably violates the usual phoenix structure and shouldn’t be done?
Do you think all queries, even the special ones, belong in the context module?

Thinking in a situation - you are not using Phoenix:

  • Your app should be complete as a whole, you can play with it via context API only.
  • Phoenix is just a kind of UI - Web UI.

Try to view:

The videos are old, but the core of wisdom doesn’t change.

Then, you will have the answer.

2 Likes

These videos were very illuminating, thank you very much!

My takeaway is the following:

  • If the query is part of my “application logic”, then it should be in the application folder.
  • If it is only part of the presentation layer, then it should be in the interface/web folder.

Example arguments could be:

  • If my app is asked for a list of posts, it should always return the latest posts of my top authors before any other posts.
    • This would be application or even business logic and should therefore be part of the PostContext.
  • This web view should show the latest posts of our top authors. The view will never be re-used in any other application (like a mobile app)”.
    • In this case, the query is unique to this single web view and does not hold any application logic. It simply serves the UI. For performance reasons, the controller uses a single query instead of composing a query from the general purpose queries of the context. This query should live in the my_app_web folder.

What are your thoughts on this?

I would like to put it into context, too. Because the query is still about data.

For example, if you are implementing two kind of UI for your app, one is GUI(such as web page), another is TUI(such as ncurses lib supported UI). Will you write the same query in two places?

I get your point and it is something that one has to very carefully discuss before making this decision. However, I took the easy way out here since I specified in my second example:

The view will never be re-used in any other application (like a mobile app)

In this case, the query is exclusive for that particular web view, which is why I argued that you could put it into the web folder. It doesn’t need to be reusable in this use-case.

1 Like

I have no problem putting logic that is really specific to a controller in the controller, or logic that is specific to a schema in the schema. I see contexts as a way to organize application business logic that is higher level than controllers and schemas. If the logic “internal” to a schema gets unwieldy, I will break the schema into multiple files, but not before that. I am wary of any kind of “purist” approaches to principles in application design. Applications are different, business cases are different. Design principles are helpful as guides not transcendent laws. Admittedly, query code in a controller might be a code smell. Code smells can indicate problems, but some code might just have a unique smell, like a fine cheese. :slight_smile:

3 Likes