Building Beacon #1 - Site Config

Blue and white striped Portuguese tiles
Leandro Pereira

Senior Software Engineer

Leandro Pereira

Build your mobile app more efficiently with LiveView Native. Book a free consult today to learn how we can put it to work for you.

Building Beacon and Beacon LiveAdmin has been an interesting journey. Even though CMS is an old concept and it may sound boring at first glance, that’s not the case for these projects. After all, we’re working with OTP, Elixir, and LiveView, all of which bring many tools we can leverage to rethink how a CMS can be built.

To make these projects happen we’ve employed some interesting patterns and techniques, some are used often in other projects and others might be a bit more unique to our use case. We’re starting a series of blog posts to share how we’re building Beacon and the inspirations for some of these techniques, starting with Beacon.Config, a relatively simple and efficient way to store and fetch every site configuration.

Whenever the processes for a new site are started (more about that in a future post), we need to provide some information to them so Beacon knows where to fetch and store data, how to handle requests, and how to perform some other essential tasks. Beacon is a library that runs in your existing Phoenix application, which is called the “host” application, and provides resources such as the Repo, Endpoint, and Router. A minimal configuration looks like this:

%Beacon.Config{
  site: :my_site,
  endpoint: MyAppWeb.Endpoint,
  router: MyAppWeb.Router,
  repo: MyApp.Repo,
  ...
}

Now that we have the Beacon.Config for the site we can fetch its properties whenever we need to perform operations that depend on the host application resource. For example, to create a new page we need to insert data into the Repo, which can be retrieved from the config with this simple call:

%Beacon.Config{repo: repo} = Beacon.Config.fetch!(:my_site)
repo

This pattern is not new, in fact, you might have used it before on Oban, which was our inspiration to implement Beacon.Config, so let’s see how it was implemented and how it works.

When the main Beacon application starts, a new Beacon.Registry process is started in the application’s supervision tree, which so far is doing nothing and has no data, but we will also start a new Supervisor for each site and make use of that Registry that was started earlier to store key/value data as site/config.

Every site in Beacon runs its own Supervisor to isolate parts of the system, but here we’re interested in the Beacon.Config process that is defined as:

{Beacon.Content, config}

And eventually started as:

def start_link(config) do
  GenServer.start_link(Beacon.Config, config, name: Beacon.Registry.via({config.site, Beacon.Config}, config))
end

Here’s where the “magic” happens. Through Registry :via we’re effectively storing the site config in the Registry for the key {:my_site, Beacon.Config}, which can be retrieved at any time as:

Beacon.Registry.lookup({:my_site, Beacon.Config})
#=> {_pid, %Beacon.Config{}}

And we can also easily fetch all sites running in the current node, which is a function used in the Beacon LiveAdmin interface to let you choose which site you want to manage.

In the next blog posts, we’ll explore the site supervisor, and other processes that are started by that supervisor.

Newsletter

Stay in the Know

Get the latest news and insights on Elixir, Phoenix, machine learning, product strategy, and more—delivered straight to your inbox.

Narwin holding a press release sheet while opening the DockYard brand kit box