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)
Thanks for the brilliant posts!
Guess, there should be
Greetings.Repo
instead ofFriends.Repo
:Yes, indeed. Typo.