apog

apog

Design pattern for a genserver that needs to get state from the database

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!

Most Liked

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

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?

Where Next?

Popular in Questions Top

9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
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
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
komlanvi
Hi everyone, I was playing with phoenix liveView but I run into an issue. I have a form and want to validate each input text when the te...
New
dotdotdotPaul
Okay, I’m having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I’m sure I’...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New

Other popular topics Top

danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 29377 241
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
chrismccord
As promised, the first release candidate of Phoenix 1.3.0 is out! This release focuses on code generators with improved project structure...
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
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 43622 214
New
JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1144 53690 245
New
AngeloChecked
What learn first? Rust or Elixir Hi Elixir community! I’m here because i want learn a new language. I’m a junior developer and mainly i ...
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New
AstonJ
Seen any cool LiveView demos, sample apps or examples? Please post them here! :003:
New

We're in Beta

About us Mission Statement