I am new to elixir and have gone through much of the initial learning materials (books and courses). As a first project, I would like to recreate this service fabric (actor model-type framework) sample application. Specifically, I am looking for advice on best practices to implement the Child-Parent Process Flow and aggregation using the elixir concurrency model. I have a way forward with the API and dashboard parts of the application using phoenix.
Sample app architecture docs
Thanks in advance
It seems like the sample uses something similar to Orleans. There is an erlang implementation of this general idea of a virtual actor as well, called
erleans. The building block of both is a so-called grain (more info on Introduction | Microsoft Orleans Documentation), which is (in my understanding) just a process whose state can be persisted to DB, stopped, and restarted at a later point in time.
It seems that what the sample app wants is to have some sort of a reverse hierarchy of these grains that wake up on incoming transactions, execute them, and “pass it up” to a parent process (possibly starting it) and go back to sleep with some updated state.
It’d be harder to do in plain elixir unless it’s ok to have all processes running all the time, even when there are no transactions to process.
Child process (e.g. GenServer) can keep a reference to the parents PID or name in it’s state. When it receives a request it processes and sends it to the parent.
Could use Registry to register and lookup names too.
Note that you can use Phoenix PubSub to message between actors. You won’t face the same restrictions listed in that doc re: only sending to clients.
It’s not strictly necessary to keep parent pids in the state, since they are already stored in the process dictionary under
$ancestors for genservers and co.
From Erlang -- proc_lib
Some useful information is initialized when a process starts. The registered names, or the process identifiers, of the parent process, and the parent ancestors, are stored together with information about the function initially called in the process.
[parent | grandparents] = Process.get(:"$ancestors") # has the whole parent chain
That’s cool, I didn’t know that.
If the supervision tree follows the diagram that would work well. Not sure if that is desired, maybe some supervisors in between but then you could grab the grandparent…
Very helpful. Thanks for the insights