SlideShare ist ein Scribd-Unternehmen logo
1 von 59
Downloaden Sie, um offline zu lesen
Phoenix per principianti
di Paolo Montrasio
paolo.montrasio@connettiva.eu
Slide a
connettiva.eu/phoenix-per-principianti.pdf
(C) Connettiva www.connettiva.eu 2
Ovvero...
2005: Conoscenza(Ruby) == 0 → Rails
2014: Conoscenza(Elixir) == 0 → Phoenix
Imparare Elixir mentre si impara Phoenix
● Le guide di Phoenix
http://www.phoenixframework.org/docs/overview
● I generatori per i controller
(C) Connettiva www.connettiva.eu 3
Per ambientarsi
● Phoenix è MVC
● È giovane ma si innalza sulle spalle dei giganti
● Per chi conosce Rails:
– Models e Controllers → Models e Controllers
– Views → Template
– Helpers → Views (circa)
– Cables (Rails 5) → Channels (da sempre)
– ActiveRecord → Ecto
– Migrations → Migrations
(C) Connettiva www.connettiva.eu 4
Differenze
● Ecto
– Lo schema va dichiarato
– Changeset per modifiche e validazioni
● Controller singolari (UserController ma /users)
● app/ → web/
● config/database.yml → config/<env>.exs
● config/routes.rb → web/router.ex
(C) Connettiva www.connettiva.eu 5
Creare una web app
http://www.phoenixframework.org/docs/up-and-ru
nning
mix phoenix.new bologna_2015
git add .gitignore config/ lib/ mix.* package.json
priv/ README.md test/ web/
git commit -a -m "Demo for today!"
(C) Connettiva www.connettiva.eu 6
config/dev.exs
config :bologna_2015, Bologna_2015.Repo,
adapter: Ecto.Adapters.Postgres,
username: "bologna_2015",
password: "RJP4Q1_2vPYX4UOR",
database: "bologna_2015_dev",
hostname: "localhost",
pool_size: 10
(C) Connettiva www.connettiva.eu 7
Lancio della web app
$ mix phoenix.run # rails s
$ iex -S mix # rails c + rails s
http://localhost:4000
$ mix -h # rake -T
(C) Connettiva www.connettiva.eu 8
Debug
iex -S mix phoenix.server
include IEx.pry
IEx.pry nel punto dove ispezionare lo stato
(C) Connettiva www.connettiva.eu 9
web/router.ex
defmodule Bologna_2015.Router do
use Bologna_2015.Web, :router
scope "/", Bologna_2015 do
pipe_through :browser
get "/", PageController, :index
resources "/users", UserController
end
end
(C) Connettiva www.connettiva.eu 10
Restful routes
$ mix phoenix.routes
page_path GET / Bologna_2015.PageController :index
user_path GET /users Bologna_2015.UserController :index
user_path GET /users/:id/edit Bologna_2015.UserController :edit
user_path GET /users/new Bologna_2015.UserController :new
user_path GET /users/:id Bologna_2015.UserController :show
user_path POST /users Bologna_2015.UserController :create
user_path PATCH /users/:id Bologna_2015.UserController :update
PUT /users/:id Bologna_2015.UserController :update
user_path DELETE /users/:id Bologna_2015.UserController :delete
(C) Connettiva www.connettiva.eu 11
Path helpers
iex(1)> Bologna_2015.Router.Helpers.user_path(
Bologna_2015.Endpoint, :index)
"/users"
iex(2)> Bologna_2015.Router.Helpers.user_path(
Bologna_2015.Endpoint, :show, 1)
"/users/1"
(C) Connettiva www.connettiva.eu 12
Più compatto
iex(3)> import Bologna_2015.Router.Helpers
nil
iex(4)> user_path(Bologna_2015.Endpoint, :index)
"/users"
iex(5)> user_path(Bologna_2015.Endpoint, :show, 1)
"/users/1"
(C) Connettiva www.connettiva.eu 13
Ancora più compatto
iex(8)> alias Bologna_2015.Endpoint
nil
iex(9)> user_path(Endpoint, :index)
"/users"
iex(10)> user_path(Endpoint, :show, 1)
"/users/1"
(C) Connettiva www.connettiva.eu 14
URL con parametri
iex(11)> user_path(Endpoint, :index,
order: "reverse")
"/users?order=reverse"
iex(12)> user_url(Endpoint, :index,
order: "reverse")
"http://localhost:4000/users?order=reverse"
(C) Connettiva www.connettiva.eu 15
Cos'è un Endpoint?
lib/bologna_2015/endpoint.ex
defmodule Bologna_2015.Endpoint do
use Phoenix.Endpoint, otp_app: :bologna_2015
…
plug Plug.Session, # i plug modificano conn
store: :cookie,
key: "_bologna_2015_key",
signing_salt: "PgkCXiY6"
plug Bologna_2015.Router # definito in web/router.ex
end
(C) Connettiva www.connettiva.eu 16
Scoping delle rotte
scope "/admin" do
resources "/users", Admin.UserController
end
user_path GET /admin/users Bologna_2015.Admin.UserController :index
user_path GET /admin/users/:id/edit Bologna_2015.Admin.UserController :edit
user_path GET /admin/users/new Bologna_2015.Admin.UserController :new
user_path GET /admin/users/:id Bologna_2015.Admin.UserController :show
user_path POST /admin/users Bologna_2015.Admin.UserController :create
user_path PATCH /admin/users/:id Bologna_2015.Admin.UserController :update
PUT /admin/users/:id Bologna_2015.Admin.UserController :update
user_path DELETE /admin/users/:id Bologna_2015.Admin.UserController :delete
(C) Connettiva www.connettiva.eu 17
I controller
def show(conn, %{"id" => id}) do
user = Repo.get!(User, id)
render(conn, "show.html", user: user)
end
o anche: conn
|> assign(:user, user)
|> render("show.html")
(C) Connettiva www.connettiva.eu 18
Dev procedurali: attenzione!
Funziona: conn
|> assign(:user, user)
|> render("show.html")
Non funziona: assign(conn, :user, user)
render(conn, "show.html")
(C) Connettiva www.connettiva.eu 19
API JSON
def show(conn, %{"id" => id}) do
user = Repo.get!(User, id)
json conn, %{ id: user.id, email: user.email, inserted_at:
user.inserted_at, updated_at: user.updated_at }
end
GET /admin/users/1
{"updated_at":"2015-10-10T09:47:04.528266Z",
"inserted_at":"2015-10-10T09:47:04.528266Z",
"id":1,"email":"paolo.montrasio@connettiva.eu"}
(C) Connettiva www.connettiva.eu 20
Redirect
def delete(conn, %{"id" => id}) do
user = Repo.get!(User, id)
Repo.delete!(user)
conn
|> put_flash(:info, "User deleted successfully.")
|> redirect(to: user_path(conn, :index))
end
(C) Connettiva www.connettiva.eu 21
Cos'è un flash?
web/templates/layout/app.html.eex
<p class="alert alert-info" role="alert">
<%= get_flash(@conn, :info) %>
</p>
<p class="alert alert-danger" role="alert">
<%= get_flash(@conn, :error) %>
</p>
<%= @inner %>
(C) Connettiva www.connettiva.eu 22
Porting di una app a Phoenix
● Customers analytics per CheckBonus
http://checkbonus.it/
● Web app Rails
● Le pagine fanno richieste
a Rails per mostrare
tabelle e grafici
● Risposte JSON
(C) Connettiva www.connettiva.eu 23
Modelli
$ mix phoenix.gen.html Retailer retailers name:string internal_id:integer
* creating web/controllers/retailer_controller.ex
* creating web/templates/retailer/edit.html.eex
* creating web/templates/retailer/form.html.eex
* creating web/templates/retailer/index.html.eex
* creating web/templates/retailer/new.html.eex
* creating web/templates/retailer/show.html.eex
* creating web/views/retailer_view.ex
* creating test/controllers/retailer_controller_test.exs
* creating priv/repo/migrations/20150919101354_create_retailer.exs
* creating web/models/retailer.ex
* creating test/models/retailer_test.exs
(C) Connettiva www.connettiva.eu 24
Rotte e migrazioni
Aggiungere resources "/retailers", RetailerController
Altrimenti:
$ mix ecto.migrate
== Compilation error on file web/controllers/retailer_controller.ex ==
** (CompileError) web/controllers/retailer_controller.ex:25: function
retailer_path/2 undefined
(stdlib) lists.erl:1337: :lists.foreach/2
(stdlib) erl_eval.erl:669: :erl_eval.do_apply/6
(elixir) lib/kernel/parallel_compiler.ex:100: anonymous fn/4 in
Kernel.ParallelCompiler.spawn_compilers/8
(C) Connettiva www.connettiva.eu 25
Migrazioni con Ecto
$ mix ecto.migrate # up
$ mix ecto.rollback # down di uno
http://hexdocs.pm/ecto/Ecto.html
Adapter per PostgreSQL, MySQL, MariaDB,
MSSQL, MongoDB.
(C) Connettiva www.connettiva.eu 26
Il modello generato
defmodule Bologna_2015.Retailer do
use Bologna_2015.Web, :model
schema "retailers" do
field :name, :string
field :internal_id, :integer
timestamps
has_many :shops, Bologna_2015.Shop
has_many :visits, Bologna_2015.Visit
end
@required_fields ~w(name)
@optional_fields ~w(internal_id)
def changeset(model, params  :empty) do
model
|> cast(params, @required_fields,
@optional_fields)
end
end
(C) Connettiva www.connettiva.eu 27
Changeset e validazioni / 1
iex(5)> alias Bologna_2015.Retailer
iex(6)> changeset = Retailer.changeset(%Retailer{}, %{})
%Ecto.Changeset{action: nil, changes: %{}, constraints: [],
errors: [name: "can't be blank"], filters: %{},
model: %Bologna_2015.Retailer{__meta__: #Ecto.Schema.Metadata<:built>,
internal_id: nil, id: nil, inserted_at: nil, name: nil, updated_at: nil},
optional: [:internal_id], opts: nil, params: %{}, repo: nil,
required: [:name],
types: %{internal_id: :integer, id: :id, inserted_at: Ecto.DateTime,
name: :string, updated_at: Ecto.DateTime}, valid?: false, validations: []}
iex(7)> changeset.valid?
false
iex(8)> changeset.errors
[name: "can't be blank"]
(C) Connettiva www.connettiva.eu 28
Changeset e validazioni / 2
iex(10)> params = %{name: "Joe Example"}
%{name: "Joe Example"}
iex(11)> changeset = Retailer.changeset(%Retailer{}, params)
%Ecto.Changeset{action: nil, changes: %{name: "Joe Example"}, constraints: [],
errors: [], filters: %{},
model: %Bologna_2015.Retailer{__meta__: #Ecto.Schema.Metadata<:built>,
internal_id: nil, id: nil, inserted_at: nil, name: nil, updated_at: nil},
optional: [:internal_id], opts: nil, params: %{"name" => "Joe Example"},
repo: nil, required: [:name],
types: %{internal_id: :integer, id: :id, inserted_at: Ecto.DateTime,
name: :string, updated_at: Ecto.DateTime}, valid?: true, validations: []}
iex(12)> changeset.valid?
true
iex(13)> changeset.errors
[ ]
(C) Connettiva www.connettiva.eu 29
Validazioni
def changeset(model, params  :empty) do
model
|> cast(params, @required_fields, @optional_fields)
|> validate_confirmation(:password)
|> validate_length(:password, min: 12)
|> validate_number(:age)
|> validate_inclusion(:age, 18..130)
|> validate_format(:email, ~r/@/)
end
(C) Connettiva www.connettiva.eu 30
Registrazione e autenticazione
● L'ostacolo più grande all'adozione di Phoenix
● No framework con copertura di tutto lo use case
– Registrazione
– Invio mail di attivazione
– Non ho ricevuto il link di attivazione
– Ho perso la password
– Faccio login / faccio logout
– Mi autentico con FB / Tw / G+ / OAuth
(C) Connettiva www.connettiva.eu 31
Soluzioni
● Addict https://github.com/trenpixster/addict
– POST JSON per registrazione, login, logout, recupero e
reset password: OK per SPA.
– Mail via Mailgun
● Passport https://github.com/opendrops/passport
– No routes, no controllers: un SessionManager da usare
nel proprio codice
● Do it yourself
http://nithinbekal.com/posts/phoenix-authentication/
(C) Connettiva www.connettiva.eu 32
Do It Yourself: solo la login
/admin/users/5
/sessions/new
/admin/users/5
/sessions/create
(C) Connettiva www.connettiva.eu 33
I file necessari
resources "/sessions", SessionController,
only: [ :new, :create, :delete ]
web/models/user.ex
web/controllers/session_controller.ex
web/views/session_view.ex
web/templates/session/new.html.eex
lib/bologna_2015/authentication.ex
lib/bologna_2015/must_be_logged_in.ex
(C) Connettiva www.connettiva.eu 34
Modello e cifratura password
schema "users" do
field :email, :string
field :encrypted_password, :string
end
@required_fields ~w(email encryped_password)
def hash(plaintext) do
Base.encode16(:crypto.hash(:sha256, to_char_list(plaintext)))
end
https://www.djm.org.uk/cryptographic-hash-functions-elixir-gener
ating-hex-digests-md5-sha1-sha2/
(C) Connettiva www.connettiva.eu 35
Inserimento utenti / 1
Barando, direttamente nel db (PostgreSQL)
create extension pgcrypto;
insert into users (email, encrypted_password,
inserted_at, updated_at) values
('paolo.montrasio@connettiva.eu',
upper(encode(digest('password', 'sha256'),'hex')),
now(), now());
(C) Connettiva www.connettiva.eu 36
Inserimento utenti / 2
Correttamente, in Elixir
$ iex -S mix
alias Bologna_2015.User
changeset = User.changeset(%User{},
%{email: "paolo.montrasio@connettiva.eu",
encrypted_password: User.hash("password")})
alias Bologna_2015.Repo
Repo.insert(changeset)
(C) Connettiva www.connettiva.eu 37
Form di login
<form action="/sessions" method="post">
<input type="hidden" name="_csrf_token"
value="<%= get_csrf_token() %>">
Email
<input name="user[email]" type="email" value="" />
Password
<input name="user[password]" type="password" />
<input type="submit" value="Sign in" />
</form>
(C) Connettiva www.connettiva.eu 38
Controller per le sessioni
def create(conn, %{ "user" => %{ "email" => email, "password" => password }}) do
case User.find(email, password) do
[user] ->
fetch_session(conn)
|> put_session(:user_id, user.id) # user.id nella sessione per i controller
|> put_flash(:info, "Login successful")
|> redirect(to: page_path(conn, :index))
[ ] ->
fetch_session(conn)
|> put_flash(:error, "Login failed")
|> redirect(to: session_path(conn, :new))
end
end
def find(email, password) do
enc_pwd = hash(password)
query = from user in User,
where: user.email == ^email and
user.encrypted_password == ^enc_pwd,
select: user
Repo.all(query)
end
(C) Connettiva www.connettiva.eu 39
Plug di autenticazione
defmodule Bologna_2015.Plugs.Authentication do
import Plug.Conn
alias Bologna_2015.User
alias Bologna_2015.Repo
def init(default), do: default
def call(conn, _default) do
user = nil
user_id = get_session(conn, :user_id)
unless user_id == nil do
user = Repo.get(User, user_id)
end
assign(conn, :current_user, user)
end
end
# conn.assigns[:current_user]
web/router.ex
defmodule Bologna_2015.Router do
use Bologna_2015.Web, :router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
plug Bologna_2015.Plugs.Authentication
end
(C) Connettiva www.connettiva.eu 40
Plug di autorizzazione
defmodule Bologna_2015.Plugs.MustBeLoggedIn do
import Plug.Conn
import Phoenix.Controller
def init(default), do: default
def call(conn, _default) do
if conn.assigns[:current_user] == nil do
conn
|> put_flash(:info, "You must be logged in")
|> redirect(to: "/") |> halt
else
conn
end
end
end
web/controllers/admin/user_controller.ex
defmodule Bologna_2015.Admin.UserController do
use Bologna_2015.Web, :controller
plug Bologna_2015.Plugs.MustBeLoggedIn
(C) Connettiva www.connettiva.eu 41
Funziona? mix test
defmodule Bologna_2015.SessionControllerTest do
use Bologna_2015.ConnCase
alias Bologna_2015.User
@valid_attrs %{"email" => "paolo.montrasio@connettiva.eu", "password" => "password"}
setup do
conn = conn()
{:ok, conn: conn}
end
test "creates session and redirects when data is valid", %{conn: conn} do
changeset = User.changeset(%User{}, %{email: @valid_attrs["email"],
encrypted_password: User.hash(@valid_attrs[“password"])})
{:ok, user } = Repo.insert(changeset)
conn = post conn, session_path(conn, :create), user: @valid_attrs
assert redirected_to(conn) == page_path(conn, :index)
assert get_session(conn, :user_id) == user.id
end
end
(C) Connettiva www.connettiva.eu 42
API JSON – di nuovo e meglio
pipeline :api do
plug :accepts, ["json"]
scope "/api", Bologna_2015, as: :api do
resources "/retailers", API.RetailerController,
only: [:index] do
resources "/visits", API.VisitController,
only: [:index]
...
api_retailer_path GET /api/retailers Bologna_2015.API.RetailerController :index
api_retailer_visit_path GET /api/retailers/:retailer_id/visits Bologna_2015.API.VisitController :index
(C) Connettiva www.connettiva.eu 43
Visit: migrazione e modello
defmodule Bologna_2015.Repo.Migrations.CreateVisit do
use Ecto.Migration
def change do
create table(:visits) do
add :retailer_id, :integer
add :started_at, :timestamp
add :duration, :integer
end
end
end
defmodule Bologna_2015.Visit do
use Bologna_2015.Web, :model
schema "visits" do
belongs_to :retailer, Bologna_2015.Retailer
field :started_at, Ecto.DateTime
field :duration, :integer
end
@required_fields ~w(retailer_id, started_at,
duration)
@optional_fields ~w()
def changeset(model, params  :empty) do
model
|> cast(params, @required_fields,
@optional_fields)
end
end
(C) Connettiva www.connettiva.eu 44
Generazione controller
mix phoenix.gen.json API.Visit visits --no-model
* creating web/controllers/api/visit_controller.ex
* creating web/views/api/visit_view.ex
* creating test/controllers/api/visit_controller_test.exs
* creating web/views/changeset_view.ex
Sostituire alias Bologna_2015.API.Visit
Con alias Bologna_2015.Visit
(C) Connettiva www.connettiva.eu 45
Il controller
def index(conn, _params) do
retailer_id = conn.assigns[:retailer].id # da dove arriva?
query = from visit in Visit,
where: visit.retailer_id == ^retailer_id,
select: visit
visits = Repo.all(query)
render(conn, "index.json", visits: visits) # dov'è il template?
end
(C) Connettiva www.connettiva.eu 46
Assign del modello
plug :assign_retailer
defp assign_retailer(conn, _options) do
retailer = Repo.get!(Bologna_2015.Retailer,
conn.params["retailer_id"])
assign(conn, :retailer, retailer)
end
(C) Connettiva www.connettiva.eu 47
Il template / 1
# web/views/api/visit_view.ex
def render("index.json", %{visits: visits}) do
%{data: render_many(visits, Bologna_2015.API.VisitView,
"visit.json")}
end
# render_many? Circa equivalente a
Enum.map(visits, fn user ->
render(Bologna_2015.API.VisitView, "visit.json”, visit: visit)
end)
(C) Connettiva www.connettiva.eu 48
Il template / 2
# web/views/api/visit_view.ex
def render("visit.json", %{visit: visit}) do
%{id: visit.id}
end
- %{id: visit.id}
+ %{started_at: visit.started_at, duration: visit.duration}
(C) Connettiva www.connettiva.eu 49
La richiesta
GET /retailers/1/visits
{"data":[
{"started_at":"2015-09-29T20:11:00Z","duration":6},
{"started_at":"2015-09-29T20:41:00Z","duration":6},
…
]}
(C) Connettiva www.connettiva.eu 50
Benchmark Phoenix
query = from visit in Visit,
where: visit.retailer_id == ^retailer_id,
select: visit
visits = Repo.all(query)
(252), 147, 134, 145, 133, 142 → media 140 ms
per 5000+ visits
(C) Connettiva www.connettiva.eu 51
Benchmark Rails
visits = Visit.where(
retailer_id: params[:retailer_id]).
pluck(:started_at, :duration)
(149), 117, 112, 124, 109, 122 → media 116 ms
(C) Connettiva www.connettiva.eu 52
Benchmark Rails
visits = Visit.where(
retailer_id: params[:retailer_id]).
pluck(:started_at, :duration)
(149), 117, 112, 124, 109, 122 → media 116 ms
Ma è un confronto onesto?
select * vs select started_at, duration
(C) Connettiva www.connettiva.eu 53
Benchmark Rails select *
visits = Visit.where(
retailer_id: params[:retailer_id])
(265), 236, 233, 230, 259, 282 → media 248 ms
(C) Connettiva www.connettiva.eu 54
Benchmark Phoenix
query = from visit in Visit,
where: visit.retailer_id == ^retailer_id,
select: [visit.started_at, visit.duration]
visits = Repo.all(query)
(193), 85, 72, 79, 70, 68 → media 74 ms
(C) Connettiva www.connettiva.eu 55
Benchmark: riassunto
select * from visits
Phoenix 140 ms
Rails 248 ms x 1.71
select started_at, duration from visits
Phoenix 74 ms
Rails 116 ms x 1.56
(C) Connettiva www.connettiva.eu 56
Benchmark: riassunto
select * from visits
Phoenix 140 ms
Rails 248 ms x 1.71
Ruby senza AR 219 ms
PostgreSQL 2.97 ms
select started_at, duration from visits
Phoenix 74 ms
Rails 116 ms x 1.56
Ruby senza AR 88 ms
PostgreSQL 3.47 ms
(C) Connettiva www.connettiva.eu 57
Fastidi
● alias / import / require all'inizio di ogni file
● Mancanza di un framework di autenticazione
● Dover chiamare ogni tanto Erlang
● Dover scrivere due volte lo schema, nella
migrazione e nel modello
(C) Connettiva www.connettiva.eu 58
Delizie
● Hot reload
● iex -S mix
● Channels
– https://medium.com/@azzarcher/the-simplicity-and
-power-of-elixir-the-ws2048-case-b510eaa568c0
(C) Connettiva www.connettiva.eu 59
Domande e contatti
Paolo Montrasio
paolo.montrasio@connettiva.eu
Slide a
connettiva.eu/phoenix-per-principianti.pdf

Weitere ähnliche Inhalte

Andere mochten auch

Cara Mengetahui Ukuran Sepatu Bola yang Pas bagi Anda
Cara Mengetahui Ukuran Sepatu Bola yang Pas bagi AndaCara Mengetahui Ukuran Sepatu Bola yang Pas bagi Anda
Cara Mengetahui Ukuran Sepatu Bola yang Pas bagi AndaBola 365
 
Adulthood and ageing
Adulthood and ageingAdulthood and ageing
Adulthood and ageingVershul Jain
 
Assighment interview
Assighment interviewAssighment interview
Assighment interviewMohamed Said
 
That's When it Happened
That's When it HappenedThat's When it Happened
That's When it HappenedMark Halvorson
 
Lec 02 (constant acc 051)
Lec 02 (constant acc 051)Lec 02 (constant acc 051)
Lec 02 (constant acc 051)nur amalina
 
Elixir's Object Oriented Layer
Elixir's Object Oriented LayerElixir's Object Oriented Layer
Elixir's Object Oriented LayerPaolo Montrasio
 
Analisis fourier 2014
Analisis fourier 2014Analisis fourier 2014
Analisis fourier 2014inawwara
 
(Group5)3 d printing
(Group5)3 d printing(Group5)3 d printing
(Group5)3 d printingNicky Wong
 
тмт нийлмэл тогтолцоо
тмт нийлмэл тогтолцоотмт нийлмэл тогтолцоо
тмт нийлмэл тогтолцооБ. Жаргалмаа
 
Asking “What?”, Automating the “How?”: The Vision of Declarative Performan...
Asking “What?”,  Automating  the “How?”: The Vision of Declarative  Performan...Asking “What?”,  Automating  the “How?”: The Vision of Declarative  Performan...
Asking “What?”, Automating the “How?”: The Vision of Declarative Performan...Jürgen Walter
 
Chatbots e dati sensibili
Chatbots e dati sensibiliChatbots e dati sensibili
Chatbots e dati sensibiliPaolo Montrasio
 
ОАО "РЖД": комплексная методика оценки руководителей структурных подразделений
ОАО "РЖД": комплексная методика оценки руководителей структурных подразделенийОАО "РЖД": комплексная методика оценки руководителей структурных подразделений
ОАО "РЖД": комплексная методика оценки руководителей структурных подразделенийЛаборатория "Гуманитарные Технологии"
 
2016/11/25 店家交流會簡報
2016/11/25 店家交流會簡報2016/11/25 店家交流會簡報
2016/11/25 店家交流會簡報LINEATTWN
 

Andere mochten auch (20)

Cara Mengetahui Ukuran Sepatu Bola yang Pas bagi Anda
Cara Mengetahui Ukuran Sepatu Bola yang Pas bagi AndaCara Mengetahui Ukuran Sepatu Bola yang Pas bagi Anda
Cara Mengetahui Ukuran Sepatu Bola yang Pas bagi Anda
 
Adulthood and ageing
Adulthood and ageingAdulthood and ageing
Adulthood and ageing
 
Quiet, god
Quiet, godQuiet, god
Quiet, god
 
Assighment interview
Assighment interviewAssighment interview
Assighment interview
 
That's When it Happened
That's When it HappenedThat's When it Happened
That's When it Happened
 
Для выступления на съезде психологов Пенсионного фонда РФ
Для выступления на съезде психологов Пенсионного фонда РФДля выступления на съезде психологов Пенсионного фонда РФ
Для выступления на съезде психологов Пенсионного фонда РФ
 
Lec 02 (constant acc 051)
Lec 02 (constant acc 051)Lec 02 (constant acc 051)
Lec 02 (constant acc 051)
 
Elixir's Object Oriented Layer
Elixir's Object Oriented LayerElixir's Object Oriented Layer
Elixir's Object Oriented Layer
 
Презентация Лаборатории "Гуманитарные Технологии"
Презентация Лаборатории "Гуманитарные Технологии"Презентация Лаборатории "Гуманитарные Технологии"
Презентация Лаборатории "Гуманитарные Технологии"
 
CaseSales: практика внешней валидизации методики
CaseSales: практика внешней валидизации методикиCaseSales: практика внешней валидизации методики
CaseSales: практика внешней валидизации методики
 
Analisis fourier 2014
Analisis fourier 2014Analisis fourier 2014
Analisis fourier 2014
 
(Group5)3 d printing
(Group5)3 d printing(Group5)3 d printing
(Group5)3 d printing
 
тмт нийлмэл тогтолцоо
тмт нийлмэл тогтолцоотмт нийлмэл тогтолцоо
тмт нийлмэл тогтолцоо
 
API less
API lessAPI less
API less
 
Asking “What?”, Automating the “How?”: The Vision of Declarative Performan...
Asking “What?”,  Automating  the “How?”: The Vision of Declarative  Performan...Asking “What?”,  Automating  the “How?”: The Vision of Declarative  Performan...
Asking “What?”, Automating the “How?”: The Vision of Declarative Performan...
 
Medicamentos embarazo
Medicamentos embarazoMedicamentos embarazo
Medicamentos embarazo
 
Chatbots e dati sensibili
Chatbots e dati sensibiliChatbots e dati sensibili
Chatbots e dati sensibili
 
ОАО "РЖД": комплексная методика оценки руководителей структурных подразделений
ОАО "РЖД": комплексная методика оценки руководителей структурных подразделенийОАО "РЖД": комплексная методика оценки руководителей структурных подразделений
ОАО "РЖД": комплексная методика оценки руководителей структурных подразделений
 
programe planning
programe planningprograme planning
programe planning
 
2016/11/25 店家交流會簡報
2016/11/25 店家交流會簡報2016/11/25 店家交流會簡報
2016/11/25 店家交流會簡報
 

Ähnlich wie Phoenix per principianti

Ctools presentation
Ctools presentationCtools presentation
Ctools presentationDigitaria
 
The state of Symfony2 - SymfonyDay 2010
The state of Symfony2 - SymfonyDay 2010The state of Symfony2 - SymfonyDay 2010
The state of Symfony2 - SymfonyDay 2010Fabien Potencier
 
More to RoC weibo
More to RoC weiboMore to RoC weibo
More to RoC weiboshaokun
 
Reactive application using meteor
Reactive application using meteorReactive application using meteor
Reactive application using meteorSapna Upreti
 
Working With The Symfony Admin Generator
Working With The Symfony Admin GeneratorWorking With The Symfony Admin Generator
Working With The Symfony Admin GeneratorJohn Cleveley
 
using Mithril.js + postgREST to build and consume API's
using Mithril.js + postgREST to build and consume API'susing Mithril.js + postgREST to build and consume API's
using Mithril.js + postgREST to build and consume API'sAntônio Roberto Silva
 
Rails antipattern-public
Rails antipattern-publicRails antipattern-public
Rails antipattern-publicChul Ju Hong
 
Rails antipatterns
Rails antipatternsRails antipatterns
Rails antipatternsChul Ju Hong
 
How to disassemble one monster app into an ecosystem of 30
How to disassemble one monster app into an ecosystem of 30How to disassemble one monster app into an ecosystem of 30
How to disassemble one monster app into an ecosystem of 30fiyuer
 
Introduction to Magento 2 module development - PHP Antwerp Meetup 2017
Introduction to Magento 2 module development - PHP Antwerp Meetup 2017Introduction to Magento 2 module development - PHP Antwerp Meetup 2017
Introduction to Magento 2 module development - PHP Antwerp Meetup 2017Joke Puts
 
Ruby on Rails : RESTful 和 Ajax
Ruby on Rails : RESTful 和 AjaxRuby on Rails : RESTful 和 Ajax
Ruby on Rails : RESTful 和 AjaxWen-Tien Chang
 
API-Entwicklung bei XING
API-Entwicklung bei XINGAPI-Entwicklung bei XING
API-Entwicklung bei XINGMark Schmidt
 
Ruby on Rails vs ASP.NET MVC
Ruby on Rails vs ASP.NET MVCRuby on Rails vs ASP.NET MVC
Ruby on Rails vs ASP.NET MVCSimone Chiaretta
 
Projet d'accès aux résultats des étudiant via client mobile
Projet d'accès aux résultats des étudiant via client mobile Projet d'accès aux résultats des étudiant via client mobile
Projet d'accès aux résultats des étudiant via client mobile Patrick Bashizi
 
Models, controllers and views
Models, controllers and viewsModels, controllers and views
Models, controllers and viewspriestc
 
Building Single Page Application (SPA) with Symfony2 and AngularJS
Building Single Page Application (SPA) with Symfony2 and AngularJSBuilding Single Page Application (SPA) with Symfony2 and AngularJS
Building Single Page Application (SPA) with Symfony2 and AngularJSAntonio Peric-Mazar
 
WebcampZG - Rails 4
WebcampZG - Rails 4WebcampZG - Rails 4
WebcampZG - Rails 4shnikola
 
Building Mobile Friendly APIs in Rails
Building Mobile Friendly APIs in RailsBuilding Mobile Friendly APIs in Rails
Building Mobile Friendly APIs in RailsJim Jeffers
 
Código Saudável => Programador Feliz - Rs on Rails 2010
Código Saudável => Programador Feliz - Rs on Rails 2010Código Saudável => Programador Feliz - Rs on Rails 2010
Código Saudável => Programador Feliz - Rs on Rails 2010Plataformatec
 

Ähnlich wie Phoenix per principianti (20)

Ctools presentation
Ctools presentationCtools presentation
Ctools presentation
 
The state of Symfony2 - SymfonyDay 2010
The state of Symfony2 - SymfonyDay 2010The state of Symfony2 - SymfonyDay 2010
The state of Symfony2 - SymfonyDay 2010
 
Devise and Rails
Devise and RailsDevise and Rails
Devise and Rails
 
More to RoC weibo
More to RoC weiboMore to RoC weibo
More to RoC weibo
 
Reactive application using meteor
Reactive application using meteorReactive application using meteor
Reactive application using meteor
 
Working With The Symfony Admin Generator
Working With The Symfony Admin GeneratorWorking With The Symfony Admin Generator
Working With The Symfony Admin Generator
 
using Mithril.js + postgREST to build and consume API's
using Mithril.js + postgREST to build and consume API'susing Mithril.js + postgREST to build and consume API's
using Mithril.js + postgREST to build and consume API's
 
Rails antipattern-public
Rails antipattern-publicRails antipattern-public
Rails antipattern-public
 
Rails antipatterns
Rails antipatternsRails antipatterns
Rails antipatterns
 
How to disassemble one monster app into an ecosystem of 30
How to disassemble one monster app into an ecosystem of 30How to disassemble one monster app into an ecosystem of 30
How to disassemble one monster app into an ecosystem of 30
 
Introduction to Magento 2 module development - PHP Antwerp Meetup 2017
Introduction to Magento 2 module development - PHP Antwerp Meetup 2017Introduction to Magento 2 module development - PHP Antwerp Meetup 2017
Introduction to Magento 2 module development - PHP Antwerp Meetup 2017
 
Ruby on Rails : RESTful 和 Ajax
Ruby on Rails : RESTful 和 AjaxRuby on Rails : RESTful 和 Ajax
Ruby on Rails : RESTful 和 Ajax
 
API-Entwicklung bei XING
API-Entwicklung bei XINGAPI-Entwicklung bei XING
API-Entwicklung bei XING
 
Ruby on Rails vs ASP.NET MVC
Ruby on Rails vs ASP.NET MVCRuby on Rails vs ASP.NET MVC
Ruby on Rails vs ASP.NET MVC
 
Projet d'accès aux résultats des étudiant via client mobile
Projet d'accès aux résultats des étudiant via client mobile Projet d'accès aux résultats des étudiant via client mobile
Projet d'accès aux résultats des étudiant via client mobile
 
Models, controllers and views
Models, controllers and viewsModels, controllers and views
Models, controllers and views
 
Building Single Page Application (SPA) with Symfony2 and AngularJS
Building Single Page Application (SPA) with Symfony2 and AngularJSBuilding Single Page Application (SPA) with Symfony2 and AngularJS
Building Single Page Application (SPA) with Symfony2 and AngularJS
 
WebcampZG - Rails 4
WebcampZG - Rails 4WebcampZG - Rails 4
WebcampZG - Rails 4
 
Building Mobile Friendly APIs in Rails
Building Mobile Friendly APIs in RailsBuilding Mobile Friendly APIs in Rails
Building Mobile Friendly APIs in Rails
 
Código Saudável => Programador Feliz - Rs on Rails 2010
Código Saudável => Programador Feliz - Rs on Rails 2010Código Saudável => Programador Feliz - Rs on Rails 2010
Código Saudável => Programador Feliz - Rs on Rails 2010
 

Mehr von Paolo Montrasio

Sviluppare agenti conversazionali con Rasa
Sviluppare agenti conversazionali con RasaSviluppare agenti conversazionali con Rasa
Sviluppare agenti conversazionali con RasaPaolo Montrasio
 
Il Chatbot come guida di viaggio: Travelchat
Il Chatbot come guida di viaggio: TravelchatIl Chatbot come guida di viaggio: Travelchat
Il Chatbot come guida di viaggio: TravelchatPaolo Montrasio
 
Costruire chatbot conversazionali ed intelligenti con Xenioo
Costruire chatbot conversazionali ed intelligenti con XeniooCostruire chatbot conversazionali ed intelligenti con Xenioo
Costruire chatbot conversazionali ed intelligenti con XeniooPaolo Montrasio
 
Voicebot: i Chatbot crescono e imparano a parlare
Voicebot: i Chatbot crescono e imparano a parlareVoicebot: i Chatbot crescono e imparano a parlare
Voicebot: i Chatbot crescono e imparano a parlarePaolo Montrasio
 
IBM Watson, un caso reale
IBM Watson, un caso realeIBM Watson, un caso reale
IBM Watson, un caso realePaolo Montrasio
 
Gardy, un chatbot multilingua
Gardy, un chatbot multilinguaGardy, un chatbot multilingua
Gardy, un chatbot multilinguaPaolo Montrasio
 
Il più intelligente Chatbot Bancario in Italia!
Il più intelligente Chatbot Bancario in Italia!Il più intelligente Chatbot Bancario in Italia!
Il più intelligente Chatbot Bancario in Italia!Paolo Montrasio
 
Chatbot, a chi proporli e come ... scegli il giusto target
Chatbot, a chi proporli e come ... scegli il giusto targetChatbot, a chi proporli e come ... scegli il giusto target
Chatbot, a chi proporli e come ... scegli il giusto targetPaolo Montrasio
 
Cicerus - una piattaforma per lo sviluppo di chatbot
Cicerus - una piattaforma per lo sviluppo di chatbotCicerus - una piattaforma per lo sviluppo di chatbot
Cicerus - una piattaforma per lo sviluppo di chatbotPaolo Montrasio
 
Esplorando Google Assistant e Dialogflow
Esplorando Google Assistant e DialogflowEsplorando Google Assistant e Dialogflow
Esplorando Google Assistant e DialogflowPaolo Montrasio
 
Hubot + wit.ai, un chatbot aziendale per Slack
Hubot + wit.ai, un chatbot aziendale per SlackHubot + wit.ai, un chatbot aziendale per Slack
Hubot + wit.ai, un chatbot aziendale per SlackPaolo Montrasio
 
L'AI per Sales & Marketing B2B
L'AI per Sales & Marketing B2BL'AI per Sales & Marketing B2B
L'AI per Sales & Marketing B2BPaolo Montrasio
 
Innovazione digitale e i chatbot 2017
Innovazione  digitale e i chatbot  2017Innovazione  digitale e i chatbot  2017
Innovazione digitale e i chatbot 2017Paolo Montrasio
 
Trovare clienti con un chatbot
Trovare clienti con un chatbotTrovare clienti con un chatbot
Trovare clienti con un chatbotPaolo Montrasio
 
Innovare la relazione con i clienti nel retail
Innovare la relazione con i clienti nel retailInnovare la relazione con i clienti nel retail
Innovare la relazione con i clienti nel retailPaolo Montrasio
 
Moose.ai: migliorare la customer experience delle banche con un chatbot B2C &...
Moose.ai: migliorare la customer experience delle banche con un chatbot B2C &...Moose.ai: migliorare la customer experience delle banche con un chatbot B2C &...
Moose.ai: migliorare la customer experience delle banche con un chatbot B2C &...Paolo Montrasio
 
Serverless chatbot: from idea to production at blazing speed
Serverless chatbot: from idea to production at blazing speedServerless chatbot: from idea to production at blazing speed
Serverless chatbot: from idea to production at blazing speedPaolo Montrasio
 

Mehr von Paolo Montrasio (20)

Sviluppare agenti conversazionali con Rasa
Sviluppare agenti conversazionali con RasaSviluppare agenti conversazionali con Rasa
Sviluppare agenti conversazionali con Rasa
 
Il Chatbot come guida di viaggio: Travelchat
Il Chatbot come guida di viaggio: TravelchatIl Chatbot come guida di viaggio: Travelchat
Il Chatbot come guida di viaggio: Travelchat
 
Costruire chatbot conversazionali ed intelligenti con Xenioo
Costruire chatbot conversazionali ed intelligenti con XeniooCostruire chatbot conversazionali ed intelligenti con Xenioo
Costruire chatbot conversazionali ed intelligenti con Xenioo
 
Diving deep into NLP
Diving deep into NLPDiving deep into NLP
Diving deep into NLP
 
Voicebot: i Chatbot crescono e imparano a parlare
Voicebot: i Chatbot crescono e imparano a parlareVoicebot: i Chatbot crescono e imparano a parlare
Voicebot: i Chatbot crescono e imparano a parlare
 
IBM Watson, un caso reale
IBM Watson, un caso realeIBM Watson, un caso reale
IBM Watson, un caso reale
 
Gardy, un chatbot multilingua
Gardy, un chatbot multilinguaGardy, un chatbot multilingua
Gardy, un chatbot multilingua
 
Il più intelligente Chatbot Bancario in Italia!
Il più intelligente Chatbot Bancario in Italia!Il più intelligente Chatbot Bancario in Italia!
Il più intelligente Chatbot Bancario in Italia!
 
Chatbot, a chi proporli e come ... scegli il giusto target
Chatbot, a chi proporli e come ... scegli il giusto targetChatbot, a chi proporli e come ... scegli il giusto target
Chatbot, a chi proporli e come ... scegli il giusto target
 
Cicerus - una piattaforma per lo sviluppo di chatbot
Cicerus - una piattaforma per lo sviluppo di chatbotCicerus - una piattaforma per lo sviluppo di chatbot
Cicerus - una piattaforma per lo sviluppo di chatbot
 
Esplorando Google Assistant e Dialogflow
Esplorando Google Assistant e DialogflowEsplorando Google Assistant e Dialogflow
Esplorando Google Assistant e Dialogflow
 
Hubot + wit.ai, un chatbot aziendale per Slack
Hubot + wit.ai, un chatbot aziendale per SlackHubot + wit.ai, un chatbot aziendale per Slack
Hubot + wit.ai, un chatbot aziendale per Slack
 
Making Chatbots
Making ChatbotsMaking Chatbots
Making Chatbots
 
L'AI per Sales & Marketing B2B
L'AI per Sales & Marketing B2BL'AI per Sales & Marketing B2B
L'AI per Sales & Marketing B2B
 
Chatbot per i musei
Chatbot per i museiChatbot per i musei
Chatbot per i musei
 
Innovazione digitale e i chatbot 2017
Innovazione  digitale e i chatbot  2017Innovazione  digitale e i chatbot  2017
Innovazione digitale e i chatbot 2017
 
Trovare clienti con un chatbot
Trovare clienti con un chatbotTrovare clienti con un chatbot
Trovare clienti con un chatbot
 
Innovare la relazione con i clienti nel retail
Innovare la relazione con i clienti nel retailInnovare la relazione con i clienti nel retail
Innovare la relazione con i clienti nel retail
 
Moose.ai: migliorare la customer experience delle banche con un chatbot B2C &...
Moose.ai: migliorare la customer experience delle banche con un chatbot B2C &...Moose.ai: migliorare la customer experience delle banche con un chatbot B2C &...
Moose.ai: migliorare la customer experience delle banche con un chatbot B2C &...
 
Serverless chatbot: from idea to production at blazing speed
Serverless chatbot: from idea to production at blazing speedServerless chatbot: from idea to production at blazing speed
Serverless chatbot: from idea to production at blazing speed
 

Kürzlich hochgeladen

Patterns for automating API delivery. API conference
Patterns for automating API delivery. API conferencePatterns for automating API delivery. API conference
Patterns for automating API delivery. API conferencessuser9e7c64
 
Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...
Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...
Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...confluent
 
Cyber security and its impact on E commerce
Cyber security and its impact on E commerceCyber security and its impact on E commerce
Cyber security and its impact on E commercemanigoyal112
 
How to submit a standout Adobe Champion Application
How to submit a standout Adobe Champion ApplicationHow to submit a standout Adobe Champion Application
How to submit a standout Adobe Champion ApplicationBradBedford3
 
英国UN学位证,北安普顿大学毕业证书1:1制作
英国UN学位证,北安普顿大学毕业证书1:1制作英国UN学位证,北安普顿大学毕业证书1:1制作
英国UN学位证,北安普顿大学毕业证书1:1制作qr0udbr0
 
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...Matt Ray
 
VK Business Profile - provides IT solutions and Web Development
VK Business Profile - provides IT solutions and Web DevelopmentVK Business Profile - provides IT solutions and Web Development
VK Business Profile - provides IT solutions and Web Developmentvyaparkranti
 
Unveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesUnveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesŁukasz Chruściel
 
20240415 [Container Plumbing Days] Usernetes Gen2 - Kubernetes in Rootless Do...
20240415 [Container Plumbing Days] Usernetes Gen2 - Kubernetes in Rootless Do...20240415 [Container Plumbing Days] Usernetes Gen2 - Kubernetes in Rootless Do...
20240415 [Container Plumbing Days] Usernetes Gen2 - Kubernetes in Rootless Do...Akihiro Suda
 
Simplifying Microservices & Apps - The art of effortless development - Meetup...
Simplifying Microservices & Apps - The art of effortless development - Meetup...Simplifying Microservices & Apps - The art of effortless development - Meetup...
Simplifying Microservices & Apps - The art of effortless development - Meetup...Rob Geurden
 
Odoo 14 - eLearning Module In Odoo 14 Enterprise
Odoo 14 - eLearning Module In Odoo 14 EnterpriseOdoo 14 - eLearning Module In Odoo 14 Enterprise
Odoo 14 - eLearning Module In Odoo 14 Enterprisepreethippts
 
Lecture # 8 software design and architecture (SDA).ppt
Lecture # 8 software design and architecture (SDA).pptLecture # 8 software design and architecture (SDA).ppt
Lecture # 8 software design and architecture (SDA).pptesrabilgic2
 
Balasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
Balasore Best It Company|| Top 10 IT Company || Balasore Software company OdishaBalasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
Balasore Best It Company|| Top 10 IT Company || Balasore Software company Odishasmiwainfosol
 
SuccessFactors 1H 2024 Release - Sneak-Peek by Deloitte Germany
SuccessFactors 1H 2024 Release - Sneak-Peek by Deloitte GermanySuccessFactors 1H 2024 Release - Sneak-Peek by Deloitte Germany
SuccessFactors 1H 2024 Release - Sneak-Peek by Deloitte GermanyChristoph Pohl
 
Cloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEECloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEEVICTOR MAESTRE RAMIREZ
 
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...OnePlan Solutions
 
Salesforce Implementation Services PPT By ABSYZ
Salesforce Implementation Services PPT By ABSYZSalesforce Implementation Services PPT By ABSYZ
Salesforce Implementation Services PPT By ABSYZABSYZ Inc
 
Exploring Selenium_Appium Frameworks for Seamless Integration with HeadSpin.pdf
Exploring Selenium_Appium Frameworks for Seamless Integration with HeadSpin.pdfExploring Selenium_Appium Frameworks for Seamless Integration with HeadSpin.pdf
Exploring Selenium_Appium Frameworks for Seamless Integration with HeadSpin.pdfkalichargn70th171
 
SensoDat: Simulation-based Sensor Dataset of Self-driving Cars
SensoDat: Simulation-based Sensor Dataset of Self-driving CarsSensoDat: Simulation-based Sensor Dataset of Self-driving Cars
SensoDat: Simulation-based Sensor Dataset of Self-driving CarsChristian Birchler
 
Precise and Complete Requirements? An Elusive Goal
Precise and Complete Requirements? An Elusive GoalPrecise and Complete Requirements? An Elusive Goal
Precise and Complete Requirements? An Elusive GoalLionel Briand
 

Kürzlich hochgeladen (20)

Patterns for automating API delivery. API conference
Patterns for automating API delivery. API conferencePatterns for automating API delivery. API conference
Patterns for automating API delivery. API conference
 
Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...
Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...
Catch the Wave: SAP Event-Driven and Data Streaming for the Intelligence Ente...
 
Cyber security and its impact on E commerce
Cyber security and its impact on E commerceCyber security and its impact on E commerce
Cyber security and its impact on E commerce
 
How to submit a standout Adobe Champion Application
How to submit a standout Adobe Champion ApplicationHow to submit a standout Adobe Champion Application
How to submit a standout Adobe Champion Application
 
英国UN学位证,北安普顿大学毕业证书1:1制作
英国UN学位证,北安普顿大学毕业证书1:1制作英国UN学位证,北安普顿大学毕业证书1:1制作
英国UN学位证,北安普顿大学毕业证书1:1制作
 
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
 
VK Business Profile - provides IT solutions and Web Development
VK Business Profile - provides IT solutions and Web DevelopmentVK Business Profile - provides IT solutions and Web Development
VK Business Profile - provides IT solutions and Web Development
 
Unveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New FeaturesUnveiling the Future: Sylius 2.0 New Features
Unveiling the Future: Sylius 2.0 New Features
 
20240415 [Container Plumbing Days] Usernetes Gen2 - Kubernetes in Rootless Do...
20240415 [Container Plumbing Days] Usernetes Gen2 - Kubernetes in Rootless Do...20240415 [Container Plumbing Days] Usernetes Gen2 - Kubernetes in Rootless Do...
20240415 [Container Plumbing Days] Usernetes Gen2 - Kubernetes in Rootless Do...
 
Simplifying Microservices & Apps - The art of effortless development - Meetup...
Simplifying Microservices & Apps - The art of effortless development - Meetup...Simplifying Microservices & Apps - The art of effortless development - Meetup...
Simplifying Microservices & Apps - The art of effortless development - Meetup...
 
Odoo 14 - eLearning Module In Odoo 14 Enterprise
Odoo 14 - eLearning Module In Odoo 14 EnterpriseOdoo 14 - eLearning Module In Odoo 14 Enterprise
Odoo 14 - eLearning Module In Odoo 14 Enterprise
 
Lecture # 8 software design and architecture (SDA).ppt
Lecture # 8 software design and architecture (SDA).pptLecture # 8 software design and architecture (SDA).ppt
Lecture # 8 software design and architecture (SDA).ppt
 
Balasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
Balasore Best It Company|| Top 10 IT Company || Balasore Software company OdishaBalasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
Balasore Best It Company|| Top 10 IT Company || Balasore Software company Odisha
 
SuccessFactors 1H 2024 Release - Sneak-Peek by Deloitte Germany
SuccessFactors 1H 2024 Release - Sneak-Peek by Deloitte GermanySuccessFactors 1H 2024 Release - Sneak-Peek by Deloitte Germany
SuccessFactors 1H 2024 Release - Sneak-Peek by Deloitte Germany
 
Cloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEECloud Data Center Network Construction - IEEE
Cloud Data Center Network Construction - IEEE
 
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
Maximizing Efficiency and Profitability with OnePlan’s Professional Service A...
 
Salesforce Implementation Services PPT By ABSYZ
Salesforce Implementation Services PPT By ABSYZSalesforce Implementation Services PPT By ABSYZ
Salesforce Implementation Services PPT By ABSYZ
 
Exploring Selenium_Appium Frameworks for Seamless Integration with HeadSpin.pdf
Exploring Selenium_Appium Frameworks for Seamless Integration with HeadSpin.pdfExploring Selenium_Appium Frameworks for Seamless Integration with HeadSpin.pdf
Exploring Selenium_Appium Frameworks for Seamless Integration with HeadSpin.pdf
 
SensoDat: Simulation-based Sensor Dataset of Self-driving Cars
SensoDat: Simulation-based Sensor Dataset of Self-driving CarsSensoDat: Simulation-based Sensor Dataset of Self-driving Cars
SensoDat: Simulation-based Sensor Dataset of Self-driving Cars
 
Precise and Complete Requirements? An Elusive Goal
Precise and Complete Requirements? An Elusive GoalPrecise and Complete Requirements? An Elusive Goal
Precise and Complete Requirements? An Elusive Goal
 

Phoenix per principianti

  • 1. Phoenix per principianti di Paolo Montrasio paolo.montrasio@connettiva.eu Slide a connettiva.eu/phoenix-per-principianti.pdf
  • 2. (C) Connettiva www.connettiva.eu 2 Ovvero... 2005: Conoscenza(Ruby) == 0 → Rails 2014: Conoscenza(Elixir) == 0 → Phoenix Imparare Elixir mentre si impara Phoenix ● Le guide di Phoenix http://www.phoenixframework.org/docs/overview ● I generatori per i controller
  • 3. (C) Connettiva www.connettiva.eu 3 Per ambientarsi ● Phoenix è MVC ● È giovane ma si innalza sulle spalle dei giganti ● Per chi conosce Rails: – Models e Controllers → Models e Controllers – Views → Template – Helpers → Views (circa) – Cables (Rails 5) → Channels (da sempre) – ActiveRecord → Ecto – Migrations → Migrations
  • 4. (C) Connettiva www.connettiva.eu 4 Differenze ● Ecto – Lo schema va dichiarato – Changeset per modifiche e validazioni ● Controller singolari (UserController ma /users) ● app/ → web/ ● config/database.yml → config/<env>.exs ● config/routes.rb → web/router.ex
  • 5. (C) Connettiva www.connettiva.eu 5 Creare una web app http://www.phoenixframework.org/docs/up-and-ru nning mix phoenix.new bologna_2015 git add .gitignore config/ lib/ mix.* package.json priv/ README.md test/ web/ git commit -a -m "Demo for today!"
  • 6. (C) Connettiva www.connettiva.eu 6 config/dev.exs config :bologna_2015, Bologna_2015.Repo, adapter: Ecto.Adapters.Postgres, username: "bologna_2015", password: "RJP4Q1_2vPYX4UOR", database: "bologna_2015_dev", hostname: "localhost", pool_size: 10
  • 7. (C) Connettiva www.connettiva.eu 7 Lancio della web app $ mix phoenix.run # rails s $ iex -S mix # rails c + rails s http://localhost:4000 $ mix -h # rake -T
  • 8. (C) Connettiva www.connettiva.eu 8 Debug iex -S mix phoenix.server include IEx.pry IEx.pry nel punto dove ispezionare lo stato
  • 9. (C) Connettiva www.connettiva.eu 9 web/router.ex defmodule Bologna_2015.Router do use Bologna_2015.Web, :router scope "/", Bologna_2015 do pipe_through :browser get "/", PageController, :index resources "/users", UserController end end
  • 10. (C) Connettiva www.connettiva.eu 10 Restful routes $ mix phoenix.routes page_path GET / Bologna_2015.PageController :index user_path GET /users Bologna_2015.UserController :index user_path GET /users/:id/edit Bologna_2015.UserController :edit user_path GET /users/new Bologna_2015.UserController :new user_path GET /users/:id Bologna_2015.UserController :show user_path POST /users Bologna_2015.UserController :create user_path PATCH /users/:id Bologna_2015.UserController :update PUT /users/:id Bologna_2015.UserController :update user_path DELETE /users/:id Bologna_2015.UserController :delete
  • 11. (C) Connettiva www.connettiva.eu 11 Path helpers iex(1)> Bologna_2015.Router.Helpers.user_path( Bologna_2015.Endpoint, :index) "/users" iex(2)> Bologna_2015.Router.Helpers.user_path( Bologna_2015.Endpoint, :show, 1) "/users/1"
  • 12. (C) Connettiva www.connettiva.eu 12 Più compatto iex(3)> import Bologna_2015.Router.Helpers nil iex(4)> user_path(Bologna_2015.Endpoint, :index) "/users" iex(5)> user_path(Bologna_2015.Endpoint, :show, 1) "/users/1"
  • 13. (C) Connettiva www.connettiva.eu 13 Ancora più compatto iex(8)> alias Bologna_2015.Endpoint nil iex(9)> user_path(Endpoint, :index) "/users" iex(10)> user_path(Endpoint, :show, 1) "/users/1"
  • 14. (C) Connettiva www.connettiva.eu 14 URL con parametri iex(11)> user_path(Endpoint, :index, order: "reverse") "/users?order=reverse" iex(12)> user_url(Endpoint, :index, order: "reverse") "http://localhost:4000/users?order=reverse"
  • 15. (C) Connettiva www.connettiva.eu 15 Cos'è un Endpoint? lib/bologna_2015/endpoint.ex defmodule Bologna_2015.Endpoint do use Phoenix.Endpoint, otp_app: :bologna_2015 … plug Plug.Session, # i plug modificano conn store: :cookie, key: "_bologna_2015_key", signing_salt: "PgkCXiY6" plug Bologna_2015.Router # definito in web/router.ex end
  • 16. (C) Connettiva www.connettiva.eu 16 Scoping delle rotte scope "/admin" do resources "/users", Admin.UserController end user_path GET /admin/users Bologna_2015.Admin.UserController :index user_path GET /admin/users/:id/edit Bologna_2015.Admin.UserController :edit user_path GET /admin/users/new Bologna_2015.Admin.UserController :new user_path GET /admin/users/:id Bologna_2015.Admin.UserController :show user_path POST /admin/users Bologna_2015.Admin.UserController :create user_path PATCH /admin/users/:id Bologna_2015.Admin.UserController :update PUT /admin/users/:id Bologna_2015.Admin.UserController :update user_path DELETE /admin/users/:id Bologna_2015.Admin.UserController :delete
  • 17. (C) Connettiva www.connettiva.eu 17 I controller def show(conn, %{"id" => id}) do user = Repo.get!(User, id) render(conn, "show.html", user: user) end o anche: conn |> assign(:user, user) |> render("show.html")
  • 18. (C) Connettiva www.connettiva.eu 18 Dev procedurali: attenzione! Funziona: conn |> assign(:user, user) |> render("show.html") Non funziona: assign(conn, :user, user) render(conn, "show.html")
  • 19. (C) Connettiva www.connettiva.eu 19 API JSON def show(conn, %{"id" => id}) do user = Repo.get!(User, id) json conn, %{ id: user.id, email: user.email, inserted_at: user.inserted_at, updated_at: user.updated_at } end GET /admin/users/1 {"updated_at":"2015-10-10T09:47:04.528266Z", "inserted_at":"2015-10-10T09:47:04.528266Z", "id":1,"email":"paolo.montrasio@connettiva.eu"}
  • 20. (C) Connettiva www.connettiva.eu 20 Redirect def delete(conn, %{"id" => id}) do user = Repo.get!(User, id) Repo.delete!(user) conn |> put_flash(:info, "User deleted successfully.") |> redirect(to: user_path(conn, :index)) end
  • 21. (C) Connettiva www.connettiva.eu 21 Cos'è un flash? web/templates/layout/app.html.eex <p class="alert alert-info" role="alert"> <%= get_flash(@conn, :info) %> </p> <p class="alert alert-danger" role="alert"> <%= get_flash(@conn, :error) %> </p> <%= @inner %>
  • 22. (C) Connettiva www.connettiva.eu 22 Porting di una app a Phoenix ● Customers analytics per CheckBonus http://checkbonus.it/ ● Web app Rails ● Le pagine fanno richieste a Rails per mostrare tabelle e grafici ● Risposte JSON
  • 23. (C) Connettiva www.connettiva.eu 23 Modelli $ mix phoenix.gen.html Retailer retailers name:string internal_id:integer * creating web/controllers/retailer_controller.ex * creating web/templates/retailer/edit.html.eex * creating web/templates/retailer/form.html.eex * creating web/templates/retailer/index.html.eex * creating web/templates/retailer/new.html.eex * creating web/templates/retailer/show.html.eex * creating web/views/retailer_view.ex * creating test/controllers/retailer_controller_test.exs * creating priv/repo/migrations/20150919101354_create_retailer.exs * creating web/models/retailer.ex * creating test/models/retailer_test.exs
  • 24. (C) Connettiva www.connettiva.eu 24 Rotte e migrazioni Aggiungere resources "/retailers", RetailerController Altrimenti: $ mix ecto.migrate == Compilation error on file web/controllers/retailer_controller.ex == ** (CompileError) web/controllers/retailer_controller.ex:25: function retailer_path/2 undefined (stdlib) lists.erl:1337: :lists.foreach/2 (stdlib) erl_eval.erl:669: :erl_eval.do_apply/6 (elixir) lib/kernel/parallel_compiler.ex:100: anonymous fn/4 in Kernel.ParallelCompiler.spawn_compilers/8
  • 25. (C) Connettiva www.connettiva.eu 25 Migrazioni con Ecto $ mix ecto.migrate # up $ mix ecto.rollback # down di uno http://hexdocs.pm/ecto/Ecto.html Adapter per PostgreSQL, MySQL, MariaDB, MSSQL, MongoDB.
  • 26. (C) Connettiva www.connettiva.eu 26 Il modello generato defmodule Bologna_2015.Retailer do use Bologna_2015.Web, :model schema "retailers" do field :name, :string field :internal_id, :integer timestamps has_many :shops, Bologna_2015.Shop has_many :visits, Bologna_2015.Visit end @required_fields ~w(name) @optional_fields ~w(internal_id) def changeset(model, params :empty) do model |> cast(params, @required_fields, @optional_fields) end end
  • 27. (C) Connettiva www.connettiva.eu 27 Changeset e validazioni / 1 iex(5)> alias Bologna_2015.Retailer iex(6)> changeset = Retailer.changeset(%Retailer{}, %{}) %Ecto.Changeset{action: nil, changes: %{}, constraints: [], errors: [name: "can't be blank"], filters: %{}, model: %Bologna_2015.Retailer{__meta__: #Ecto.Schema.Metadata<:built>, internal_id: nil, id: nil, inserted_at: nil, name: nil, updated_at: nil}, optional: [:internal_id], opts: nil, params: %{}, repo: nil, required: [:name], types: %{internal_id: :integer, id: :id, inserted_at: Ecto.DateTime, name: :string, updated_at: Ecto.DateTime}, valid?: false, validations: []} iex(7)> changeset.valid? false iex(8)> changeset.errors [name: "can't be blank"]
  • 28. (C) Connettiva www.connettiva.eu 28 Changeset e validazioni / 2 iex(10)> params = %{name: "Joe Example"} %{name: "Joe Example"} iex(11)> changeset = Retailer.changeset(%Retailer{}, params) %Ecto.Changeset{action: nil, changes: %{name: "Joe Example"}, constraints: [], errors: [], filters: %{}, model: %Bologna_2015.Retailer{__meta__: #Ecto.Schema.Metadata<:built>, internal_id: nil, id: nil, inserted_at: nil, name: nil, updated_at: nil}, optional: [:internal_id], opts: nil, params: %{"name" => "Joe Example"}, repo: nil, required: [:name], types: %{internal_id: :integer, id: :id, inserted_at: Ecto.DateTime, name: :string, updated_at: Ecto.DateTime}, valid?: true, validations: []} iex(12)> changeset.valid? true iex(13)> changeset.errors [ ]
  • 29. (C) Connettiva www.connettiva.eu 29 Validazioni def changeset(model, params :empty) do model |> cast(params, @required_fields, @optional_fields) |> validate_confirmation(:password) |> validate_length(:password, min: 12) |> validate_number(:age) |> validate_inclusion(:age, 18..130) |> validate_format(:email, ~r/@/) end
  • 30. (C) Connettiva www.connettiva.eu 30 Registrazione e autenticazione ● L'ostacolo più grande all'adozione di Phoenix ● No framework con copertura di tutto lo use case – Registrazione – Invio mail di attivazione – Non ho ricevuto il link di attivazione – Ho perso la password – Faccio login / faccio logout – Mi autentico con FB / Tw / G+ / OAuth
  • 31. (C) Connettiva www.connettiva.eu 31 Soluzioni ● Addict https://github.com/trenpixster/addict – POST JSON per registrazione, login, logout, recupero e reset password: OK per SPA. – Mail via Mailgun ● Passport https://github.com/opendrops/passport – No routes, no controllers: un SessionManager da usare nel proprio codice ● Do it yourself http://nithinbekal.com/posts/phoenix-authentication/
  • 32. (C) Connettiva www.connettiva.eu 32 Do It Yourself: solo la login /admin/users/5 /sessions/new /admin/users/5 /sessions/create
  • 33. (C) Connettiva www.connettiva.eu 33 I file necessari resources "/sessions", SessionController, only: [ :new, :create, :delete ] web/models/user.ex web/controllers/session_controller.ex web/views/session_view.ex web/templates/session/new.html.eex lib/bologna_2015/authentication.ex lib/bologna_2015/must_be_logged_in.ex
  • 34. (C) Connettiva www.connettiva.eu 34 Modello e cifratura password schema "users" do field :email, :string field :encrypted_password, :string end @required_fields ~w(email encryped_password) def hash(plaintext) do Base.encode16(:crypto.hash(:sha256, to_char_list(plaintext))) end https://www.djm.org.uk/cryptographic-hash-functions-elixir-gener ating-hex-digests-md5-sha1-sha2/
  • 35. (C) Connettiva www.connettiva.eu 35 Inserimento utenti / 1 Barando, direttamente nel db (PostgreSQL) create extension pgcrypto; insert into users (email, encrypted_password, inserted_at, updated_at) values ('paolo.montrasio@connettiva.eu', upper(encode(digest('password', 'sha256'),'hex')), now(), now());
  • 36. (C) Connettiva www.connettiva.eu 36 Inserimento utenti / 2 Correttamente, in Elixir $ iex -S mix alias Bologna_2015.User changeset = User.changeset(%User{}, %{email: "paolo.montrasio@connettiva.eu", encrypted_password: User.hash("password")}) alias Bologna_2015.Repo Repo.insert(changeset)
  • 37. (C) Connettiva www.connettiva.eu 37 Form di login <form action="/sessions" method="post"> <input type="hidden" name="_csrf_token" value="<%= get_csrf_token() %>"> Email <input name="user[email]" type="email" value="" /> Password <input name="user[password]" type="password" /> <input type="submit" value="Sign in" /> </form>
  • 38. (C) Connettiva www.connettiva.eu 38 Controller per le sessioni def create(conn, %{ "user" => %{ "email" => email, "password" => password }}) do case User.find(email, password) do [user] -> fetch_session(conn) |> put_session(:user_id, user.id) # user.id nella sessione per i controller |> put_flash(:info, "Login successful") |> redirect(to: page_path(conn, :index)) [ ] -> fetch_session(conn) |> put_flash(:error, "Login failed") |> redirect(to: session_path(conn, :new)) end end def find(email, password) do enc_pwd = hash(password) query = from user in User, where: user.email == ^email and user.encrypted_password == ^enc_pwd, select: user Repo.all(query) end
  • 39. (C) Connettiva www.connettiva.eu 39 Plug di autenticazione defmodule Bologna_2015.Plugs.Authentication do import Plug.Conn alias Bologna_2015.User alias Bologna_2015.Repo def init(default), do: default def call(conn, _default) do user = nil user_id = get_session(conn, :user_id) unless user_id == nil do user = Repo.get(User, user_id) end assign(conn, :current_user, user) end end # conn.assigns[:current_user] web/router.ex defmodule Bologna_2015.Router do use Bologna_2015.Web, :router pipeline :browser do plug :accepts, ["html"] plug :fetch_session plug :fetch_flash plug :protect_from_forgery plug :put_secure_browser_headers plug Bologna_2015.Plugs.Authentication end
  • 40. (C) Connettiva www.connettiva.eu 40 Plug di autorizzazione defmodule Bologna_2015.Plugs.MustBeLoggedIn do import Plug.Conn import Phoenix.Controller def init(default), do: default def call(conn, _default) do if conn.assigns[:current_user] == nil do conn |> put_flash(:info, "You must be logged in") |> redirect(to: "/") |> halt else conn end end end web/controllers/admin/user_controller.ex defmodule Bologna_2015.Admin.UserController do use Bologna_2015.Web, :controller plug Bologna_2015.Plugs.MustBeLoggedIn
  • 41. (C) Connettiva www.connettiva.eu 41 Funziona? mix test defmodule Bologna_2015.SessionControllerTest do use Bologna_2015.ConnCase alias Bologna_2015.User @valid_attrs %{"email" => "paolo.montrasio@connettiva.eu", "password" => "password"} setup do conn = conn() {:ok, conn: conn} end test "creates session and redirects when data is valid", %{conn: conn} do changeset = User.changeset(%User{}, %{email: @valid_attrs["email"], encrypted_password: User.hash(@valid_attrs[“password"])}) {:ok, user } = Repo.insert(changeset) conn = post conn, session_path(conn, :create), user: @valid_attrs assert redirected_to(conn) == page_path(conn, :index) assert get_session(conn, :user_id) == user.id end end
  • 42. (C) Connettiva www.connettiva.eu 42 API JSON – di nuovo e meglio pipeline :api do plug :accepts, ["json"] scope "/api", Bologna_2015, as: :api do resources "/retailers", API.RetailerController, only: [:index] do resources "/visits", API.VisitController, only: [:index] ... api_retailer_path GET /api/retailers Bologna_2015.API.RetailerController :index api_retailer_visit_path GET /api/retailers/:retailer_id/visits Bologna_2015.API.VisitController :index
  • 43. (C) Connettiva www.connettiva.eu 43 Visit: migrazione e modello defmodule Bologna_2015.Repo.Migrations.CreateVisit do use Ecto.Migration def change do create table(:visits) do add :retailer_id, :integer add :started_at, :timestamp add :duration, :integer end end end defmodule Bologna_2015.Visit do use Bologna_2015.Web, :model schema "visits" do belongs_to :retailer, Bologna_2015.Retailer field :started_at, Ecto.DateTime field :duration, :integer end @required_fields ~w(retailer_id, started_at, duration) @optional_fields ~w() def changeset(model, params :empty) do model |> cast(params, @required_fields, @optional_fields) end end
  • 44. (C) Connettiva www.connettiva.eu 44 Generazione controller mix phoenix.gen.json API.Visit visits --no-model * creating web/controllers/api/visit_controller.ex * creating web/views/api/visit_view.ex * creating test/controllers/api/visit_controller_test.exs * creating web/views/changeset_view.ex Sostituire alias Bologna_2015.API.Visit Con alias Bologna_2015.Visit
  • 45. (C) Connettiva www.connettiva.eu 45 Il controller def index(conn, _params) do retailer_id = conn.assigns[:retailer].id # da dove arriva? query = from visit in Visit, where: visit.retailer_id == ^retailer_id, select: visit visits = Repo.all(query) render(conn, "index.json", visits: visits) # dov'è il template? end
  • 46. (C) Connettiva www.connettiva.eu 46 Assign del modello plug :assign_retailer defp assign_retailer(conn, _options) do retailer = Repo.get!(Bologna_2015.Retailer, conn.params["retailer_id"]) assign(conn, :retailer, retailer) end
  • 47. (C) Connettiva www.connettiva.eu 47 Il template / 1 # web/views/api/visit_view.ex def render("index.json", %{visits: visits}) do %{data: render_many(visits, Bologna_2015.API.VisitView, "visit.json")} end # render_many? Circa equivalente a Enum.map(visits, fn user -> render(Bologna_2015.API.VisitView, "visit.json”, visit: visit) end)
  • 48. (C) Connettiva www.connettiva.eu 48 Il template / 2 # web/views/api/visit_view.ex def render("visit.json", %{visit: visit}) do %{id: visit.id} end - %{id: visit.id} + %{started_at: visit.started_at, duration: visit.duration}
  • 49. (C) Connettiva www.connettiva.eu 49 La richiesta GET /retailers/1/visits {"data":[ {"started_at":"2015-09-29T20:11:00Z","duration":6}, {"started_at":"2015-09-29T20:41:00Z","duration":6}, … ]}
  • 50. (C) Connettiva www.connettiva.eu 50 Benchmark Phoenix query = from visit in Visit, where: visit.retailer_id == ^retailer_id, select: visit visits = Repo.all(query) (252), 147, 134, 145, 133, 142 → media 140 ms per 5000+ visits
  • 51. (C) Connettiva www.connettiva.eu 51 Benchmark Rails visits = Visit.where( retailer_id: params[:retailer_id]). pluck(:started_at, :duration) (149), 117, 112, 124, 109, 122 → media 116 ms
  • 52. (C) Connettiva www.connettiva.eu 52 Benchmark Rails visits = Visit.where( retailer_id: params[:retailer_id]). pluck(:started_at, :duration) (149), 117, 112, 124, 109, 122 → media 116 ms Ma è un confronto onesto? select * vs select started_at, duration
  • 53. (C) Connettiva www.connettiva.eu 53 Benchmark Rails select * visits = Visit.where( retailer_id: params[:retailer_id]) (265), 236, 233, 230, 259, 282 → media 248 ms
  • 54. (C) Connettiva www.connettiva.eu 54 Benchmark Phoenix query = from visit in Visit, where: visit.retailer_id == ^retailer_id, select: [visit.started_at, visit.duration] visits = Repo.all(query) (193), 85, 72, 79, 70, 68 → media 74 ms
  • 55. (C) Connettiva www.connettiva.eu 55 Benchmark: riassunto select * from visits Phoenix 140 ms Rails 248 ms x 1.71 select started_at, duration from visits Phoenix 74 ms Rails 116 ms x 1.56
  • 56. (C) Connettiva www.connettiva.eu 56 Benchmark: riassunto select * from visits Phoenix 140 ms Rails 248 ms x 1.71 Ruby senza AR 219 ms PostgreSQL 2.97 ms select started_at, duration from visits Phoenix 74 ms Rails 116 ms x 1.56 Ruby senza AR 88 ms PostgreSQL 3.47 ms
  • 57. (C) Connettiva www.connettiva.eu 57 Fastidi ● alias / import / require all'inizio di ogni file ● Mancanza di un framework di autenticazione ● Dover chiamare ogni tanto Erlang ● Dover scrivere due volte lo schema, nella migrazione e nel modello
  • 58. (C) Connettiva www.connettiva.eu 58 Delizie ● Hot reload ● iex -S mix ● Channels – https://medium.com/@azzarcher/the-simplicity-and -power-of-elixir-the-ws2048-case-b510eaa568c0
  • 59. (C) Connettiva www.connettiva.eu 59 Domande e contatti Paolo Montrasio paolo.montrasio@connettiva.eu Slide a connettiva.eu/phoenix-per-principianti.pdf