Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

110
OTP, Phoenix & Ecto: Three Pillars of Elixir Elixir Club Ternopil, 2017

Transcript of Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Page 1: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

OTP, Phoenix & Ecto: Three Pillars of Elixir

Elixir Club Ternopil, 2017

Page 2: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Erlang

Functional Programming in Erlang

www.futurelearn.com/courses/functional-programming-erlang/

Concurrent Programming in Erlang

https://www.futurelearn.com/courses/concurrent-programming-erlang

Learn You Some Erlang for great good!

http://learnyousomeerlang.com/

Page 3: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Overview

1. Processes & OTP

2. Phoenix Elixir web framework

3. Ecto database wrapper and language integrated query for Elixir

Page 4: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Erlang/Elixir processes

• All code runs inside processes

• Processes are isolated from each other, run concurrent to one another and communicate via message passing (Actor model)

• Processes are extremely lightweight in terms of memory and CPU and managed by Erlang VM

• It is common to have tens or even hundreds of thousands of processes running simultaneously

Page 5: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Spawning basic process

iex> spawn fn -> 100 * 100 end

#PID<0.94.0>

iex> self()

#PID<0.80.0>

Page 6: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Sending and receiving messages

iex> parent = self()

#PID<0.80.0>

iex> spawn fn -> send(parent, {:hello, self()}) end

#PID<0.103.0>

iex> receive do

...> {:hello, pid} -> "Got hello from #{inspect pid}"

...> {:other, _} -> "Something other"

...> end

"Got hello from #PID<0.103.0>"

Page 7: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Receive timeout

iex> receive do

...> {:other, msg} -> msg

...> after

...> 1_000 -> "Nothing received after 1s"

...> end

"Nothing received after 1s"

Page 8: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Flush

iex> send self(), :hello

:hello

iex> send self(), :world

:world

iex> flush()

:hello

:world

:ok

Page 9: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Linked processes: spawn_link

iex> spawn_link fn -> raise "something bad happened" end

23:53:50.503 [error] Process #PID<0.93.0> raised an exception

** (RuntimeError) something bad happened

:erlang.apply/2

** (EXIT from #PID<0.91.0>) an exception was raised:

** (RuntimeError) something bad happened

:erlang.apply/2

Page 10: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Linked processes & “Failing fast” philosophy

Parent process, which is the shell process, has received an EXIT signalfrom another process causing the parent process to terminate.

Often we will link our processes to supervisors which will detect when a process dies and start a new process in its place.

In Elixir we are actually fine with letting processes fail because we expect supervisors to properly restart our systems.

Page 11: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Elixir: Task

iex> task = Task.async(fn -> 100 * 100 end)

%Task{owner: #PID<0.94.0>, pid: #PID<0.96.0>, ref: #Reference<0.0.1.139>}

iex> res = Task.await(task)

10000

Page 12: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Statedef start_link do

Task.start_link(fn -> loop(%{}) end)

end

defp loop(map) do

receive do

{:get, key, caller} ->

send caller, Map.get(map, key)

loop(map)

{:put, key, value} ->

loop(Map.put(map, key, value))

end

end

Page 13: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

State

Spawn LoopInit Exit

SendReceive

Page 14: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Elixir: Agent

iex> {:ok, agent} = Agent.start_link(fn -> %{} end)

{:ok, #PID<0.88.0>}

iex> Agent.update(agent, &Map.put(&1, :hello, "world"))

:ok

iex> Agent.get(agent, &Map.get(&1, :hello))

"world"

Page 15: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Behaviours

• Many of the processes have similar structures, they follow similar patterns

• Behaviours provide a way to define a set of functions that have to be implemented by a module

• You can think of behaviours like interfaces in OO languages

Page 16: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Behaviours

defmodule GenServer do

@callback init(args :: term) :: {:ok, state} ...

@callback handle_call(request :: term, from, state :: term)

:: {:reply, reply, new_state} ...

@callback handle_cast(request :: term, state :: term)

:: {:noreply, new_state} ...

...

Page 17: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Implementing behaviours

...# Callbacks

def handle_call(:pop, _from, [h | t]) do{:reply, h, t}

end

def handle_cast({:push, item}, state) do{:noreply, [item | state]}

end...

Page 18: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

GenServer

• “Generic servers” (processes) that encapsulate state, provide sync and async calls, support code reloading, and more.

• The GenServer behaviour abstracts the common client-server interaction. Developers are only required to implement the callbacks and functionality they are interested in.

• A GenServer is a process like any other Elixir process and it can be used to keep state, execute code asynchronously and so on.

Page 19: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

GenServer exampledefmodule Stack do

use GenServer

# Callbacks

def handle_call(:pop, _from, [h | t]) do

{:reply, h, t}

end

def handle_cast({:push, item}, state) do

{:noreply, [item | state]}

end

end

Page 20: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

GenServer example

iex> {:ok, pid} = GenServer.start_link(Stack, [:hello])

iex> GenServer.call(pid, :pop)

:hello

iex> GenServer.cast(pid, {:push, :world})

:ok

iex>GenServer.call(pid, :pop)

:world

Page 21: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

GenServer cheatsheet

Benjamin Tan Wei Hao

https://github.com/benjamintanweihao/elixir-cheatsheets

Page 22: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Supervision Trees

• Supervision trees are a nice way to structure fault-tolerant applications.

• Process structuring model based on the idea of workers and supervisors.

S

W

S

W

S

W

Page 23: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Supervision Trees

• Workers are processes that perform computations, that is, they do the actual work.

• Supervisors are processes that monitor the behaviour of workers. A supervisor can restart a worker if something goes wrong.

• The supervision strategy dictates what happens when one of the children crashes.

Page 24: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Supervisor

• A behaviour module for implementing supervision functionality.

• A supervisor is a process which supervises other processes, which we refer to as child processes.

Page 25: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Supervisor module

defmodule MyApp.Supervisor do

use Supervisor

def start_link do

Supervisor.start_link(__MODULE__, [])

end

def init([]) do

children = [ worker(Stack, [[:hello]]) ]

supervise(children, strategy: :one_for_one)

end

end

Page 26: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Supervisor Cheat Sheet

Benjamin Tan Wei Hao

https://github.com/benjamintanweihao/elixir-cheatsheets

Page 27: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Application behaviour

• In Erlang/OTP, an application is a component implementing some specific functionality, that can be started and stopped as a unit

• Mix is responsible for compiling your source code and generating your application .app file in Elixir.

• Mix is also responsible for configuring, starting and stopping your application and its dependencies (mix.exs).

• .app holds our application definition

Page 28: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Application callback

defmodule MyApp do

use Application

def start(_type, _args) do

MyApp.Supervisor.start_link()

end

end

Page 29: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Application project

mix new hello_world --sup hello_world

|-- README.md

|-- config

| `-- config.exs

|-- lib

| |-- hello_world

| | `-- application.ex

| `-- hello_world.ex

|-- mix.exs

`-- test

|-- hello_world_test.exs

`-- test_helper.exs

Page 30: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Application project: configuration

hello_world\mix.exs

def application do

# Specify extra applications you'll use from Erlang/Elixir[extra_applications: [:logger],

mod: {HelloWorld.Application, []}]

end

Page 31: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Application project: callback modulehello_world\lib\hello_world\application.ex

defmodule HelloWorld.Application do

...

def start(_type, _args) do

import Supervisor.Spec, warn: false

children = []

opts = [strategy: :one_for_one, name: HelloWorld.Supervisor]

Supervisor.start_link(children, opts)

end

end

Page 32: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Umbrella projects

mix new hello_umbrella --umbrella

hello_umbrella

|-- README.md

|-- apps

|-- config

| `-- config.exs

`-- mix.exs

Page 33: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Umbrella projects: configuration

...

def project do

[apps_path: "apps",

build_embedded: Mix.env == :prod,

start_permanent: Mix.env == :prod,

deps: deps()]

end

...

Page 34: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

In umbrella dependencies

Mix supports an easy mechanism to make one umbrella child depend on another.

...

defp deps do

[{:hello_world, in_umbrella: true}]

end

...

Page 35: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

GenStage & Flow

Announcing GenStage

http://elixir-lang.org/blog/2016/07/14/announcing-genstage/

GenStage and Flow - Jose Valim | Elixir Club 5

https://www.youtube.com/watch?v=IUrfcBwkm7w

https://hex.pm/packages/gen_stage

Page 36: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

GenStage is a new Elixir behaviour for exchanging events with back-pressure between Elixir processes

producerproducer consumer

producer consumer

consumer

Page 37: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Flow: concurrent data processingdef process_flow(path_to_file) do

path_to_file

|> File.stream!()

|> Flow.from_enumerable()

|> Flow.flat_map(&String.split/1)

|> Flow.map(&String.replace(&1, ~r/\W/u, ""))

|> Flow.filter_map(fn w -> w != "" end, &String.downcase/1)

|> Flow.partition()

|> Flow.reduce(fn -> %{} end, fn word, map ->

Map.update(map, word, 1, &(&1 + 1))

end)

|> Enum.into(%{})

end

Page 38: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Flow: concurrent data processingdef process_flow(path_to_file) do

path_to_file

|> File.stream!()

|> Flow.from_enumerable()

|> Flow.flat_map(&String.split/1)

|> Flow.map(&String.replace(&1, ~r/\W/u, ""))

|> Flow.filter_map(fn w -> w != "" end, &String.downcase/1)

|> Flow.partition()

|> Flow.reduce(fn -> %{} end, fn word, map ->

Map.update(map, word, 1, &(&1 + 1))

end)

|> Enum.into(%{})

end

P

PC PC

DemandDispatcher

PartitionDispatcher

PC PC

C CReducers%{} %{}

Page 39: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

FlowP

PC PC

PC PC

C C%{} %{}

"The Project Gutenberg EBook of The Complete Works of William Shakespeare, by\n"

"William Shakespeare\n"

"The", "Project", "Gutenberg", "EBook", "of", "The", "Complete", "Works", "of", "William", "Shakespeare,", "by"

"William", "Shakespeare"

"the", "project", "of", “the", "william", "of", "by ", "william"

"gutenberg", "ebook", "complete", "shakespeare", "works", "shakespeare"

Page 40: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Flow

Experimental.Flow, Yurii Bodarev at KyivElixirMeetup 3.1

https://www.youtube.com/watch?v=XhUeSUFF06w

https://github.com/yuriibodarev/elixir_flow

Page 41: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Phoenix Framework

• Phoenix is a web development framework written in Elixir which implements the server-side MVC pattern

• Phoenix provides the best of both worlds - high developer productivity and high application performance

• Phoenix is actually the top layer of a multi-layer system designed to be modular and flexible. The other layers include Plug, and Ecto

• The Erlang HTTP server, Cowboy, acts as the foundation for Plug and Phoenix

Page 42: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Phoenix Framework

• The Plug

• The Endpoint

• The Router

• Controllers

• Actions

• Views

• Templates

• Channels

Page 43: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

The Plug

• Plug is a specification for constructing composable modules to build web applications.

• Plugs are reusable modules or functions built to that specification.

• They provide discrete behaviors - like request header parsing or logging.

• Because the Plug API is small and consistent, plugs can be defined and executed in a set order, like a pipeline.

• Core Phoenix components like Endpoints, Routers, and Controllers are all just Plugs internally

Page 44: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Module Plug example

defmodule Example.HelloWorldPlug do

import Plug.Conn

def init(options), do: options

def call(conn, _opts) do

conn

|> put_resp_content_type("text/plain")

|> send_resp(200, "Hello World!")

end

end

%Plug.Conn{…}

Page 45: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Plug pipelines

pipeline :browser do

plug :accepts, ["html"]

plug :fetch_session

plug :fetch_flash

plug :protect_from_forgery

plug :put_secure_browser_headers

end

Page 46: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Module Plug example

...

@locales ["en", "fr", "de"]

def init(default), do: default

def call(%Plug.Conn{params: %{"locale" => loc}} = conn, _default)

when loc in @locales

do

assign(conn, :locale, loc)

end

def call(conn, default), do: assign(conn, :locale, default)

...

Page 47: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Adding Plug to the pipeline

pipeline :browser do

plug :accepts, ["html"]

plug :fetch_session

plug :fetch_flash

plug :protect_from_forgery

plug :put_secure_browser_headers

plug PhoenixApp.Plugs.Locale, "en"

end

Page 48: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

The Endpoint

• provides a wrapper for starting and stopping the endpoint as part of a supervision tree;

• handles all aspects of requests up until the point where the router takes over

• to define an initial plug pipeline where requests are sent through;

• to host web specific configuration for your application.

• dispatches requests into a designated router

Page 49: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

The Endpointphoenix_app\lib\phoenix_app\web\endpoint.ex

defmodule PhoenixApp.Web.Endpoint do

use Phoenix.Endpoint, otp_app: :phoenix_app

# plug ...

# plug ...

plug PhoenixApp.Web.Router

end

Page 50: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Starting Endpointphoenix_app\lib\phoenix_app\application.ex

...

children = [

# Start the Ecto repository

supervisor(PhoenixApp.Repo, []),

# Start the endpoint when the application startssupervisor(PhoenixApp.Web.Endpoint, [])

]

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

...

Page 51: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

The Router

• parses incoming requests and dispatches them to the correct controller/action, passing parameters as needed

• provides helpers to generate route paths or urls to resources

• defines named pipelines through which we may pass our requests

Page 52: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

The Routerphoenix_app\lib\phoenix_app\web\router.ex

...

pipeline :browser do

plug :accepts, ["html"]

...

plug :put_secure_browser_headers

end

scope "/", PhoenixApp.Web do

pipe_through :browser # Use the default browser stack

get "/", PageController, :index

end

...

Page 53: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Controllers & Actions

Controllers provide functions, called actions, to handle requests

Actions

• prepare data and pass it into views

• invoke rendering via views

• perform redirects

Page 54: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Controllerget "/pages/:id", PageController, :show

phoenix_app\lib\phoenix_app\web\controllers\page_controller.ex

defmodule PhoenixApp.Web.PageController do

use PhoenixApp.Web, :controller

def show(conn, %{"id" => id}) do

user = Accounts.get_user(id)

render(conn, "show.html", user: user)

end

end

Page 55: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

web\web.exuse PhoenixApp.Web, :controller

phoenix_app\lib\phoenix_app\web\web.ex...

def controller do

quote do

use Phoenix.Controller, namespace: PhoenixApp.Web

import Plug.Conn

import PhoenixApp.Web.Router.Helpers

import PhoenixApp.Web.Gettext

end

end

...

Page 56: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Views

• Defines the view layer of a Phoenix application

• Render templates

• Define helper functions, available in templates, to decorate data for presentation

Page 57: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Rendering Templates

Phoenix assumes a strong naming convention from controllers to views to the templates they render. The PageController requires a PageViewto render templates in the \web\templates\page directory.

phoenix_app\lib\phoenix_app\web\web.ex

...

def view do quote do

use Phoenix.View, root: "lib/phoenix_app/web/templates", namespace: PhoenixApp.Web

...

Page 58: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Rendering Templates

phoenix_app\lib\phoenix_app\web\views\page_view.ex

defmodule PhoenixApp.Web.PageView do

use PhoenixApp.Web, :view

end

Phoenix.View will automatically load all templates at “phoenix_app\lib\phoenix_app\web\templates\page” and include them in the PhoenixApp.Web.PageView

Page 59: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Rendering JSON

def render("index.json", %{pages: pages}) do

%{data: render_many(pages, PhoenixApp.PageView, "page.json")}

end

def render("show.json", %{page: page}) do

%{data: render_one(page, PhoenixApp.PageView, "page.json")}

end

def render("page.json", %{page: page}) do

%{title: page.title}

end

Page 60: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Templates

foo.html.eex

• templates are precompiled and fast

• template name - is the name of the template as given by the user, without the template engine extension, for example: “foo.html”

• template path - is the complete path of the template in the filesystem, for example, “path/to/foo.html.eex”

• template root - the directory where templates are defined

• template engine (EEx)- a module that receives a template path and transforms its source code into Elixir quoted expressions.

Page 61: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Template examples

Hello <%= @name %>

<h3>Keys for the conn Struct</h3>

<%= for key <- connection_keys @conn do %>

<p><%= key %></p>

<% end %>

function that returns List of keys

Page 62: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Channels

• manage sockets for easy real-time communication

• are analogous to controllers except that they allow bi-directional communication with persistent connections

• Every time you join a channel, you need to choose which particular topic you want to listen to. The topic is just an identifier, but by convention it is often made of two parts: "topic:subtopic".

Page 63: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Channel endpoint

phoenix_app\lib\phoenix_app\web\endpoint.ex

socket "/socket", PhoenixApp.Web.UserSocket

phoenix_app\lib\phoenix_app\web\channels\user_socket.ex

channel "room:*", PhoenixApp.Web.RoomChannel

Any topic coming into the router with the "room:" prefix would dispatch to MyApp.RoomChannel

Page 64: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Joining Channels

defmodule PhoenixApp.Web.RoomChannel douse Phoenix.Channel

def join("room:lobby", _message, socket) do{:ok, socket}

end

def join("room:" <> _private_room_id, _params, _socket) do{:error, %{reason: "unauthorized"}} end

end

Page 65: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

JS

phoenix_app\assets\js\socket.js...

socket.connect()

// Now that you are connected, you can join channels with a topic:

let channel = socket.channel("topic:subtopic", {}) channel.join()

.receive("ok", resp => { console.log("Joined successfully", resp) })

.receive("error", resp => { console.log("Unable to join", resp) })

Page 66: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

JS

channel.push("new_msg", {body: “Hello world!”})

channel.on("new_msg", payload => { … }) …

Page 67: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Incoming Events

We handle incoming events with handle_in/3. We can pattern match on the event names, like “new_msg”

def handle_in("new_msg", %{"body" => body}, socket) do

broadcast! socket, "new_msg", %{body: body}

{:noreply, socket} end

Page 68: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Outgoing Events: default implementation

def handle_out("new_msg", payload, socket) do

push socket, "new_msg", payload

{:noreply, socket} end

Page 69: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Intercepting Outgoing Events

intercept ["smth_important"]

def handle_out("smth_important", msg, socket) do

if … do

{:noreply, socket}

else

push socket, " smth_important", msg

{:noreply, socket}

endend

Page 70: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Phoenix Framework 1.3

v1.3.0-rc.1

https://hex.pm/packages/phoenix

phx.new project generator

mix archive.install https://github.com/phoenixframework/archives/raw/master/phx_new.ez

Page 71: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Phoenix Framework 1.3

Lonestar ElixirConf 2017- KEYNOTE: Phoenix 1.3 by Chris McCord

https://www.youtube.com/watch?v=tMO28ar0lW8

Phoenix v1.3.0-rc.0 released @ ElixirForum

https://elixirforum.com/t/phoenix-v1-3-0-rc-0-released/3947

Upcoming book “Programming Phoenix 1.3” @ ElixirForum

https://elixirforum.com/t/programming-phoenix-1-3/2469

Page 72: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir
Page 73: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

1.2: mix phoenix.new phoenix_oldphoenix_old

|-- config

|-- lib

| `-- phoenix_old

|-- priv

|-- test

`-- web

|-- channels

|-- controllers

|-- models

|-- static

|-- templates

`-- views

Page 74: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

1.3: mix phx.new phoenix_new

phoenix_new

|-- assets

|-- config

|-- lib

| `-- phoenix_new

| `-- web

| |-- channels

| |-- controllers

| |-- templates

| `-- views

|-- priv

`-- test

Page 75: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

1.3: mix phx.new phoenix --umbrella

phoenix_umbrella

|-- apps

| |-- phoenix

| `-- phoenix_web

phoenix_umbrella\apps\phoenix_web\mix.exs

...

{:phoenix, in_umbrella: true},

...

Page 76: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Web Namespace

defmodule PhoenixApp.Web.PageController do

use PhoenixApp.Web, :controller

def index(conn, _params) do

render conn, "index.html"

endend

Page 77: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Context

1.2: mix phoenix.gen.json User users email:string

Repo.get(User, id)

1.3: mix phx.gen.json Accounts User users email:string

Accounts.get_user(2) iex> %User{email: [email protected]}

Accounts.create_user(params) iex> {:ok, new_user}

Page 78: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

1.2: Models

`-- web

|-- channels

|-- controllers

|-- models

| |-- comment.ex

| |-- invoice.ex

| |-- order.ex

| |-- payment.ex

| |-- post.ex

| `-- user.ex

|-- static

|-- templates

`-- views

Page 79: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

1.3: Context

|-- lib

| `-- phoenix_new

| |-- blog

| | |-- blog.ex

| | |-- comment.ex

| | `-- post.ex

| |-- sales

| | |-- order.ex

| | |-- payment.ex

| | `-- sales.ex

| `-- web

| |-- channels

Page 80: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

The boundary for the Sales system.lib\phoenix_new\sales\sales.ex

defmodule PhoenixNew.Sales do

def list_orders do

Repo.all(Order)

end

def get_order!(id), do: Repo.get!(Order, id)

def create_order(attrs \\ %{}) do

%Order{} |> order_changeset(attrs) |> Repo.insert()

end

...

Page 81: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Action Fallback

On Phoenix 1.2, every controller needed to return a valid %Plug.Conn{} for every request. Otherwise, an exception would rise.

On Phoenix 1.3 we can register the plug to call as a fallback to the controller action. If the controller action fails to return a %Plug.Conn{}, the provided plug will be called and receive the controller’s %Plug.Conn{} as it was before the action was invoked along with the value returned from the controller action.

Page 82: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

1.2: Controller Actionphoenix_old\web\controllers\post_controller.excase Repo.insert(changeset) do

{:ok, post} ->

conn

|> put_status(:created)

|> put_resp_header("location", post_path(conn, :show, post))

|> render("show.json", post: post)

{:error, changeset} ->

conn

|> put_status(:unprocessable_entity)

|> render(PhoenixOld.ChangesetView, "error.json", changeset:changeset)

end

Page 83: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

1.3: Controller Action

phoenix_new\lib\phoenix_new\web\controllers\post_controller.ex

def create(conn, %{"post" => post_params}) do

with {:ok, %Post{} = post} <- Blog.create_post(post_params) do

conn

|> put_status(:created)

|> put_resp_header("location", post_path(conn, :show, post))

|> render("show.json", post: post)

end

end

Page 84: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

1.3 Action Fallback

phoenix_new\lib\phoenix_new\web\controllers\fallback_controller.ex

def call(conn, {:error, %Ecto.Changeset{} = changeset}) do

conn

|> put_status(:unprocessable_entity)

|> render(PhoenixNew.Web.ChangesetView, "error.json", changeset:changeset)

end

Page 85: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Ecto

Domain specific language for writing queries and interacting with databases in Elixir.

pages.plataformatec.com.br/ebook-whats-new-in-ecto-2-0

Page 86: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Ecto

Ecto is split into 4 main components:

• Ecto.Repo - repositories are wrappers around the data store.

• Ecto.Schema - schemas are used to map any data source into an Elixir struct.

• Ecto.Changeset - allow developers to filter, cast, and validate changes before we apply them to the data.

• Ecto.Query - written in Elixir syntax, queries are used to retrieve information from a given repository.

Page 87: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Repositories

Via the repository, we can create, update, destroy and query existing database entries.

Page 88: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Repositories

Ecto.Repo is a wrapper around the database. We can define a repository as follows:

defmodule Blog.Repo do

use Ecto.Repo, otp_app: :blog

end

Page 89: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Repositories

A repository needs an adapter and credentials to communicate to the database. Configuration for the Repo usually defined in your config/config.exs:

config :blog, Blog.Repo,

adapter: Ecto.Adapters.Postgres,

database: "blog_repo",

username: "postgres",

password: "postgres",

hostname: "localhost"

Page 90: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

RepositoriesEach repository in Ecto defines a start_link/0. Usually this function is invoked as part of your application supervision tree:

def start(_type, _args) do

import Supervisor.Spec, warn: false

children = [ worker(Blog.Repo, []), ]

opts = [strategy: :one_for_one, name: Blog.Supervisor]

Supervisor.start_link(children, opts)

end

Page 91: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Schema

Schemas allows developers to define the shape of their data.

defmodule Blog.User do

use Ecto.Schema

schema "users" do

field :name, :string

field :reputation, :integer, default: 0

has_many :posts, Blog.Post, on_delete: :delete_all

timestamps

end

end

Page 92: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Schema

By defining a schema, Ecto automatically defines a struct:

iex> user = %Blog.User{name: "Bill"}

%Blog.User{__meta__: #Ecto.Schema.Metadata<:built, "users">, id: nil, inserted_at: nil, name: "Bill"}, posts: #Ecto.Association.NotLoaded<association :posts is not loaded>, reputation: 0, updated_at:nil}

Page 93: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Schema

Using Schema we can interact with a repository:

iex> user = %Blog.User{name: "Bill", reputation: 10}

%Blog.User{…}

iex> Blog.Repo.insert!(user)

%Blog.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">, id: 6, inserted_at: ~N[2016-12-13 16:16:35.983000], name: "Bill", posts:#Ecto.Association.NotLoaded<association :posts is not loaded>, reputation: 10, updated_at: ~N[2016-12-13 16:16:36.001000]}

Page 94: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Schema

# Get the user back

iex> newuser = Blog.Repo.get(Blog.User, 6)

iex> newuser.id

6

# Delete it

iex> Blog.Repo.delete(newuser)

{:ok, %Blog.User{…, id: 6,…}}

Page 95: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Schema

We can use pattern matching on Structs created with Schemas:

iex> %{name: name, reputation: reputation} =...> Blog.Repo.get(Blog.User, 1)

iex> name

"Alex"

iex> reputation

144

Page 96: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Changesets

We can add changesets to our schemas to validate changes before we apply them to the data:

def changeset(user, params \\ %{}) do

user

|> cast(params, [:name, :reputation])

|> validate_required([:name, :reputation])

|> validate_inclusion(:reputation, -999..999)

end

Page 97: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Changesets

iex> alina = %Blog.User{name: "Alina"}

iex> correct_changeset = Blog.User.changeset(alina, %{reputation: 55})

#Ecto.Changeset<action: nil, changes: %{reputation: 55}, errors: [], data: #Blog.User<>, valid?: true>

iex> invalid_changeset = Blog.User.changeset(alina, %{reputation: 1055})

#Ecto.Changeset<action: nil, changes: %{reputation: 1055}, errors:[reputation: {"is invalid", [validation: :inclusion]}], data:#Blog.User<>, valid?: false>

Page 98: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Changeset with Repository functions

iex> valid_changeset.valid?

true

iex> Blog.Repo.insert(valid_changeset)

{:ok, %Blog.User{…, id: 7, …}}

Page 99: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Changeset with Repository functions

iex> invalid_changeset.valid?

false

iex> Blog.Repo.insert(invalid_changeset)

{:error, #Ecto.Changeset<action: :insert, changes: %{reputation: 1055}, errors: [reputation: {"is invalid", [validation: :inclusion]}], data:#Blog.User<>, valid?: false>}

Page 100: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Changeset with Repository functions

case Blog.Repo.update(changeset) do

{:ok, user} ->

# user updated

{:error, changeset} ->

# an error occurred

end

Page 101: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

We can provide different changeset functions for different use cases

def registration_changeset(user, params) do

# Changeset on create

end

def update_changeset(user, params) do

# Changeset on update

end

Page 102: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Query

Ecto allows you to write queries in Elixir and send them to the repository, which translates them to the underlying database.

Page 103: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Query using predefined Schema

# Query using predefined Schemaquery = from u in User,

where: u.reputation > 35, select: u

# Returns %User{} structs matching the queryRepo.all(query)

[%Blog.User{…, id: 2, …, name: "Bender", …, reputation: 42, …},

%Blog.User{…, id: 1, …, name: "Alex", …, reputation: 144, …}]

Page 104: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Direct query with “users” table

# Directly querying the “users” table

query = from u in "users",

where: u.reputation > 30,

select: %{name: u.name, reputation: u.reputation}

# Returns maps as defined in select

Repo.all(query)

[%{name: "Bender", reputation: 42}, %{name: "Alex", reputation: 144}]

Page 105: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

External values in Queries

# ^ operator

min = 33

query = from u in "users",

where: u.reputation > ^min,

select: u.name

# casting

mins = "33"

query = from u in "users",

where: u.reputation > type(^mins, :integer),

select: u.name

Page 106: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

External values in Queries

If the query is made against Schema than Ecto will automatically cast external value

min = "35"

Repo.all(from u in User, where: u.reputation > ^min)

You can also skip Select to retrieve all fields specified in the Schema

Page 107: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Ecto Multi

Ecto.Multi is a data structure for grouping multiple Repo operations in a single database transaction.

def reset(account, params) do

Multi.new

|> Multi.update(:account, Account.password_reset_changeset(account, params))

|> Multi.insert(:log, Log.password_reset_changeset(account, params))

|> Multi.delete_all(:sessions, Ecto.assoc(account, :sessions))

end

Repo.transaction(PasswordManager.reset(account, params))

Page 108: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

Ecto Multi

case result do

{:ok, %{account: account, log: log, sessions: sessions}} ->

# We can access results under keys we used

# for naming the operations.

{:error, failed_operation, failed_value, changes_so_far} ->

# One of the operations failed.

# We can access the operation's failure value (changeset)

# Successful operations would have been rolled back.

end

Page 109: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

BooksSaša Jurić “Elixir in Action”

https://www.manning.com/books/elixir-in-action

Benjamin Tan Wei Hao “The Little Elixir & OTP Guidebook”

https://www.manning.com/books/the-little-elixir-and-otp-guidebook

New! Lance Halvorsen “Functional Web Development with Elixir, OTP, and Phoenix”

https://pragprog.com/book/lhelph/functional-web-development-with-elixir-otp-and-phoenix

Chris McCord “Programming Phoenix” (1.2 -> 1.3)

https://pragprog.com/book/phoenix/programming-phoenix

“What's new in Ecto 2.0”

http://pages.plataformatec.com.br/ebook-whats-new-in-ecto-2-0

Page 110: Yurii Bodarev - OTP, Phoenix & Ecto: Three Pillars of Elixir

THANK YOU!Yurii Bodarev

@bodarev_yurii

https://github.com/yuriibodarev