DEV Community

Milan Pevec
Milan Pevec

Posted on

Elixir, Ecto, OTP? The beginner's view, Part II.

In Part I of this series, we were researching the OTP concepts for the single purpose, to understand initial steps of Ecto integration. What was missing was the understanding of the Elixir Application concept.

Elixir application ?

In the examples used in Part I, we have been actually working inside of the application the entire time!

When we run mix new app, mix creates application for us. And whenever we run iex -S mix, mix compiles, and then even starts our app by default. In our previous examples, we just haven't implemented the so-called application callback yet. A callback to run/start something, like e.g. Greetings.Supervisor, when our application is started.

Let's add one.

If we open a file mix.exs and check the following function:

def application do
  [
    extra_applications: [:logger]
  ]
end

we see that there is an option with key extra_applications defined. The value represents the list of the applications that needs to be started before our app is started. You are right if you're thinking that iex is starting :logger application too.

Now let's add the application callback:

def application do
  [
    extra_applications: [:logger],
    mod: {App, []}
  ]
end

With the mod keyword we are defining which Elixir module is our application callback module. You can not put here whatever callback you want, but only elixir module that uses Application behavior:

defmodule App do
  use Application

  def start(_type, _args) do
    Greetings.Supervisor.start_link
  end
end

Here we used start/2 callback, which will be called when the application starts and inside we are calling our Greetings.Supervisor.start_link, like we did before manually.

Let's try it. After we call iex -S mix, which compiles and runs our application with application callback, we can do directly our API calls:

iex(1)> Greetings.Server.add(:supervised_greetings_genserver, 1, "Hi")
:ok
iex(2)> Greetings.Server.add(:supervised_greetings_genserver, 2, "Hello")
:ok
iex(3)> Greetings.Server.list(:supervised_greetings_genserver)           
%{1 => "Hi", 2 => "Hello"}

Sweet. We didn't need to do any calls related to the supervisor or similar, everything was done for us in the callback.

You can see more details about our app in the so-called application definition file, which is actually an artifact created by the compilation process. The file is located in _build/dev/lib/example-app/example-app.app. There you can see the list of all applications to be run before, names, etc.

A lit a bit of a different taste

As we saw, we created a supervisor module in the previous example of the Part I and we used the start_link/3 function:

start_link(module, init_arg, options \ [])

This is so-called module-based supervisor.

But there is, again, a shorter way, where we can use start_link/2 function directly in our application callback module. That means we can remove our supervisor module file and do the following:

defmodule App do
  use Application

  def start(_type, _args) do
    children = [
        {Greetings.Server, name: :supervised_greetings_genserver}
    ]

    # name is optional
    opts = [strategy: :one_for_one, name: Greetings.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

As we can see, now we can call start_link/2 directly and we made our code even cleaner!

Why are we showing all this? If you remember, our mission is to understand Ecto initial steps as a total outsider to Elixir. Lets do the recap now.

Recap

So far we saw how the Elixir syntax for Ecto config is used. We saw the basics of the OTP and communication between processes. And above we saw the concept of the Application - we saw everything in order to understand initial steps for using Ecto. Let's use this knowledge and start from scratch.

First, we use mix and create new application with the support of the supervisors (and supervision trees):

mix new greetings sup

This will create Greetings.Application with Application behavior, will add application callback in the mix.exs and implement the application callback function with calling start_link of Supervisor.

mix.exs:

...
def application do
  [
    extra_applications: [:logger],
    mod: {Greetings.Application, []}
  ]
end
...

application.ex:

defmodule Greetings.Application do
  use Application

  def start(_type, _args) do
    children = []

    opts = [strategy: :one_for_one, name: Greetings.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

The list of its children (workers) is still empty at this point, we will complete it later.

Now let's add Ecto specifics. First let's add dependencies in the mix.exs:

defp deps do
  [
    {:ecto_sql, "~> 3.0"},
    {:postgrex, ">= 0.0.0"}
  ]
end

and install them with running mix deps.get.

Now let's create Ecto configuration and repository by running the following line:

mix ecto.gen.repo -r Greetings.Repo

This will create a lot of artifacts, but what's important now is that configuration is added in the config.exs:

import Config

config :greetings, Greetings.Repo,
  database: "greetings_repo",
  username: "user",
  password: "pass",
  hostname: "localhost"

Where we can put appropriate data for our database.

Also the Ecto.Repo behavior module was created:

defmodule Greetings.Repo do
  use Ecto.Repo,
    otp_app: :greetings,
    adapter: Ecto.Adapters.Postgres
end

Because we can have more repositories and we want our application to know about them all, we need to add a newly created repository to the list in the config. So we add in the config.exs the following:

config :greetings,
  ecto_repos: [Friends.Repo]

Last step is to add our Ecto repository as a child of the application supervisor, as we saw before with our Greeting.Server:

defmodule Greetings.Application do
  use Application

  def start(_type, _args) do
    children = [Greetings.Repo]

    opts = [strategy: :one_for_one, name: Greetings.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

That's it! We put a lot of effort into understanding all the concepts, but I hope now it's more clear, at least it is for me.

Now let's try it out. We run iex -S mix (as we know now, this will do all the hard work) and run a DB query:

iex(1)> Ecto.Adapters.SQL.query(Greetings.Repo, "SELECT now()", [])

09:46:51.405 [debug] QUERY OK db=9.6ms decode=1.2ms queue=19.7ms idle=1231.4ms
SELECT now() []
{:ok,
 %Postgrex.Result{
   columns: ["now"],
   command: :select,
   connection_id: 86343,
   messages: [],
   num_rows: 1,
   rows: [[~U[2020-06-25 07:46:51.371536Z]]]
 }}

That's it, we manage to get to the end of our mission.

Top comments (2)

Collapse
 
psylone profile image
Evgeniy Fateev

Thanks for the brilliant posts!

Guess, there should be Greetings.Repo instead of Friends.Repo:

config :greetings,
  ecto_repos: [Friends.Repo]
Enter fullscreen mode Exit fullscreen mode
Collapse
 
mpevec9 profile image
Milan Pevec

Yes, indeed. Typo.