apog

apog

I’ve been making a basic todo-list application to learn phoenix and i’ve been looking through the forums for answers on how to properly design a phoenix application. The consensus seems to be to only make calls to the database through Repo in either the controller or some separate service object dedicated to that task. But there doesn’t seem to be a consensus on a design pattern for getting such data to other modules that need it.

In my case I have a genserver (called Todo.Server) that has a specific name and keeps its own list of todo-items in it’s state. It’s started from a dynamic supervisor. My issue comes comes in here: when Todo.Server is started I check the database for it’s initial state. When an entry is added I have it persist to the database as well as update it’s local state.

From what I’ve been seeing this is a bad practice for a phoenix app, and it sounds like the convention is to have that initial state passed in from a higher level. But this makes things more complex. I would then have to first find out if a server with the given name is already running (since it only reads from the database on startup, otherwise it uses it’s local state), and if not then make a call to Repo to get the data, and then pass it all the way down to the server. And where would that logic live? That logic seems very out of place for a controller. Does this mean I should make a separate service object responsible for getting the data needed for the dynamic supervisor and the Todo servers and pass it to them through the controller?

I’ve been having a very difficult time figuring out how to properly handle such a situation in phoenix, any help would be greatly appreciated!

Showing Posts 1 to 10

peerreynders

peerreynders

If I understand you correctly it sounds like Ecto was installed as part of the Phoenix project setup.

The “pattern” that you seem to be looking for would make Ecto part of the Todo application - not Phoenix.

The “pattern” is demonstrated in

hangman is the equivalent to your Todo application. gallows is the Phoenix based web interface for hangman. Now hangman doesn’t use a database - but if it did Ecto would be part of hangman - not gallows.

Therefore your Todo application should be designed to use Ecto (or whatever other persistent storage you use) even before Phoenix get involved.


You list Elixir in Action 2e as one of your books. If you look at the Chapter 11 example you have:

Now the big difference here is that it’s organized as one single Mix project. The gallows/hangman approach would organize Todo.Server and Todo.Database in a separate OTP application (i.e. separate Mix project) that can then be used as a dependency for Todo.Web (in a different Mix project).

Similarly a Phoenix application could simply use a “Todo OTP Application” (that uses Ecto internally) as a dependency without directly getting Ecto involved. Meanwhile the Phoenix project acts as “the application” that starts everything up but the “Todo Application” is responsible for managing its persistent storage (e.g. through Ecto or possibly yet another OTP application).

However that is probably the most complicated way of using Phoenix.

  • You can build “Phoenix is your application” style applications where simply each request to the web server initiates some interaction with the database that results in a response.

  • The next level of refinement is to use “Phoenix contexts” - i.e. organizing code into domain/business (i.e. context) specific modules rather than simply leaving all the code in the various controllers.

  • For even better separation there are umbrella projects which allows multiple OTP applications to run under the same configuration.

  • Finally the gallows/hangman approach which relies on bare path dependencies (which maximizes decoupling but makes many things less convenient (tradeoffs …)).

kokolegorille

kokolegorille

There is nothing wrong doing a todo list in phoenix without using gen_server, for example using data from db.

IIRC there is no ecto involved in the todo list of Elixir in Action.

In case You want to do both… for example having a gen_server loading state from db, there is a recommandation, try to have the quickest init possible. You can achieve this like that.

  @impl GenServer
  def init(args) do
    send(self(), {:set_state, args})
    # Do not use timeout here, it will be send by set_state
    {:ok, fresh_state(args)}
  end

  # Initialize handler, separate from init for fast init unlock.
  @impl GenServer
  def handle_info({:set_state, args}, state) do

    # Do the loading here! You might return state from db queries

    {:noreply, state, @timeout}
  end

What would be a service object in FP?

david_ex

david_ex

Note that if you’re using OTP >= 21, you can use handle_continue to avoid race conditions when deferring your initialization:

  def init(args) do
    state = fresh_state(args)
    # Do not use timeout here, it will be send by set_state
    {:ok, , state, {:continue, {:init_state, args}}}
  end

  # Initialize handler, separate from init for fast init unlock.
  def handle_continue({:init_state, args}, state) do

    # Do the loading here! You might return state from db queries

    {:noreply, state, @timeout}
  end

The advantage of this is that when using named processes, it prevents a message from being processed after init finished, but before the :set_state message is handled. Using continue will ensure the code to finish initialization is run before accepting a new message from the mailbox.

More info here.

Note that if you want to be able to @impl ... the handle_continue/2 function, you need to have Elixir >= 1.7

apog

apog OP

Thank you for all of the feedback! Yeah my todo application is based off of the one from Elixir in Action 2e and I was trying to modify it to make use the phoenix framework. But it looks like I am currently building phoenix as my application and I need to simply view it as a web interface (this is very new to me coming from a rails background) and keep the Todo app as it’s own separate thing. If I am understanding what you are saying, there is nothing wrong with me making a call to Repo from directly within my Todo.server genserver rather than having that be passed in?

apog

apog OP

The gen_server was for efficiency since after it’s started I can get data from its state rather than hitting the db every time. And yeah Elixir in Action just uses file IO as the database, but I modified the project to see how adding a relation database would work. And I should have said ‘service module’ instead of ‘object’. What I meant was a module dedicated to making calls to Repo for data. (i.e. if I had some complicated query for getting a combination of lists, it could live in the service module rather than the query happening directly in the controller)

kokolegorille

kokolegorille

Which is what contexts are made for :slight_smile:

apog

apog OP

ah, i’m still getting the terminology down. I think contexts are what I mean. So I guess my question boiled down to whether I should get the data from within a context and pass it to the genserver through the supervisor, or if it’s okay to just get the data from directly within a genserver. It’s also just confusing that the built in generators for phoenix go against the recommended design of an application. For example, based on what peerreynders said above, I wouldn’t want any of this to live in the phoenix app and so the phx.gen.context command would actually be guiding me in the wrong direction.

kokolegorille

kokolegorille

Not really, it creates the context in the module connected to ecto.

If You create an app, You will have app, and app_web. And contexts are generated app side.

Phoenix still is an interface for your application, separated from your business logic.

peerreynders

peerreynders

It depends a bit on the architectural style that you are using.

  • “Phoenix is your Application” (kinda “Rails-style”) wouldn’t bother with caching the todo list in a process and would interact straight with the database. In memory caching isn’t always a total win (unless the data is entirely ephemeral).

  • The Elixir In Action 2e “Database” uses the file system - but for all Todo.Server knows it could be using Ecto/PostgreSQL. With that in mind there is some value in hiding the details from Todo.Server behind a Todo.Database module which is the only one who knows about Ecto, the Repo and the queries. Most people are not willing to go to that extreme as it cuts them off from the functionality in Ecto.Changeset for data validation.


apog

apog OP

Well part of my question is trying to figure out what architectural style to use. I was hoping there was some sort of convention in the phoenix community around where to access the database and was the general structure should be.

For your second bullet, why wouldn’t you still be able to use Ecto.Changeset for data validation? The database module would still check the validity of the data before calling repo to persist.

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
jonnycharles
I’m in search of an Elixir library that offers PDF generation capabilities similar to Ruby’s Prawn. While there have been discussions abo...
New
spammy
I’m looking to build a personal workflow to quickly deploy web applications written in elixir/phoenix, for local consumption (ie not on t...
New
dli
Before I dive in myself, did anyone successfully sprinkle Hologram into their existing LiveView app? Looking for hints regarding: Addi...
New
roeland
Kia ora, We have been using elixir-google-api to connect to Google Drive. However, with the updates to Tesla due to CVEs this is now bro...
New
bottlenecked
Hi all, I wanted to ask how the community is dealing with post-release steps. Today we have Ecto migrations, which make sure that the db...
New
rahultumpala
Hello, I have an Elixir backend that implements a custom protocol over TCP. I want to load test the backend and assess the performance o...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
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
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
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
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews