Elixir Nodes are Cool

I had a bit of a problem. I was writing an app that would send me email reminders. It would store the reminder data in an ETS table and check it every 24 hours to see if were any reminders to send. The app would run quietly as a background process in my computer as long as the computer was running. Now I wanted it to read events from a csv file to add into the table. Of course, one way was to pass in the csv file as a config option that would be read when the app starts or set the app to check the csv file for changes. But I did not want it to as I felt that reading a csv file everytime it started was worse than initializing from a DETS table, which is what I went for. I wanted the app to read a csv file only when I tell it to. Also I wanted to be able to add new events or reset the database any time I wanted without stopping the app.

How to do this? I didn’t want to repeat code for reading and writing to the table in a separate script, I wanted to use the code I already had in the main app. What was already built in to the language I could use to solve this requirement?

Behold! The Node module. The solution to all your local independent BEAM instance communication problems. If you had multiple BEAM instances (be it Elixir or Erlang apps and servers) running on your computer (or multiple computers) and they know each other’s names, then they can talk to each other.

So I wrote an Elixir script that would register itself with one name using Node.start, start the app with another name and wait for a message from the app saying that it is ready. Then, if you had passed any options to the script, it will run those actions as remote calls on the main app using Node.spawn. It’s good practice to make sure that your remote calls are actual functions defined in the main app. You cannot pass an anonymous function to the remote node via Node.spawn from your script if that Node.spawn call is inside a module in your script (see this issue to learn more).

# you can do this if the Bar module is defined in the remote node's code
defmodule Foo do
  def action do
    Node.spawn(:remote_node@localhost, Bar, :run, [opts: {1, 2}])
  end
end

# But you can't do this if the module Foo is not defined in the 
# remote node, even if the Bar module has been defined in the
# remote node's code
defmodule Foo do
  def action do
    Node.spawn(:remote_node@localhost, fn -> Bar.run(opts: {1, 2}) end)
  end
end

Before anyone says anything, yes I know that this isn’t anything brand new and game changing. RPC has existed for decades. But I just thought this was a neat feature of the ecosystem and I wanted to share. So here you go! Do with it what you will.