Tony Messias
Maceió DEV Meetup #21
Phoenix for Laravel
developers
This is not a hands-on talk.
I'm not going to teach you Elixir (or
Phoenix) in 30~ish minutes.
I'm not saying that one is
better than the other.
In fact, I'm actually showing that you can
build shiny things with tools you already
know.
This talk in a mind-map.
Functional
Programming
Functional
Programming
Haven't the Gods of programming decided
that OOP has won this battle?
Why is functional programming
in such a “hype” now?
What is a function?
● In math, functions are all about transforming input to output (sounds familiar?)
● You might be used to expressing functions in math like so:
y = x * 2 or g = x ^ 2
● But there is another notation that is much more expressive (IMO):
f(x) = x * 2
f(5) = 5 * 2
f(5) = 10
f(x) = x * 2
---
const f = (x) => x * 2
var x = 5
assertEquals(f(x), 10)
assertEquals(f(x), f(x))
source
class Value {
constructor(val) {
this.value = val;
}
}
const f = (x) => x.value++ * 2
const x = new Value(5);
console.log(f(x) == f(x)); // True or False?
console.log(x.value); // What is the value?
class Value {
constructor(val) {
this.value = val;
}
}
const f = (x) => x.value++ * 2
const x = new Value(5);
console.log(f(x) == f(x)); // false
console.log(x.value); // 7
Immutability ease
distributing computation.
Concurrency
and
Parallelism
Parallelism Concurrency
source
before I forget...
Slack'ish in Laravel
tonysm/slackish-laravel
Slack'ish in Phoenix
tonysm/slackish-phoenix
Elixir, the good parts
defmodule SlackishphxWeb.CompanyController do
use SlackishphxWeb, :controller
plug SlackishphxWeb.Plugs.RequireAuth
def create(conn, %{"company" => params}) do
case Slackishphx.Auth.create_company(conn.assigns[:user], params) do
{:ok, company} ->
conn
|> create_default_channel(company)
|> switch_company(conn.assigns[:user], company)
{:error, changeset} ->
conn
|> render("new.html", changeset: changeset)
end
end
end
defmodule SlackishphxWeb.CompanyController do
use SlackishphxWeb, :controller
plug SlackishphxWeb.Plugs.RequireAuth
def create(conn, %{"company" => params}) do
case Slackishphx.Auth.create_company(conn.assigns[:user], params) do
{:ok, company} ->
conn
|> create_default_channel(company)
|> switch_company(conn.assigns[:user], company)
{:error, changeset} ->
conn
|> render("new.html", changeset: changeset)
end
end
end
defmodule SlackishphxWeb.CompanyController do
use SlackishphxWeb, :controller
plug SlackishphxWeb.Plugs.RequireAuth
def create(conn, %{"company" => params}) do
case Slackishphx.Auth.create_company(conn.assigns[:user], params) do
{:ok, company} ->
conn
|> create_default_channel(company)
|> switch_company(conn.assigns[:user], company)
{:error, changeset} ->
conn
|> render("new.html", changeset: changeset)
end
end
end
Phoenix, the good parts
MVC
Phoenix is not your application.
slackishphx/lib
├── slackishphx
│ ├── application.ex
│ ├── auth
│ │ ├── auth.ex
│ │ ├── company.ex
│ │ └── user.ex
│ ├── chat
│ │ ├── channel.ex
│ │ └── chat.ex
│ └── repo.ex
├── slackishphx.ex
├── slackishphx_web
│ ├── channels
│ ├── controllers
│ ├── endpoint.ex
│ ├── gettext.ex
│ ├── plugs
│ ├── router.ex
│ ├── templates
│ └── views
└── slackishphx_web.ex
slackishphx/lib
├── slackishphx
│ ├── application.ex
│ ├── auth
│ │ ├── auth.ex
│ │ ├── company.ex
│ │ └── user.ex
│ ├── chat
│ │ ├── channel.ex
│ │ └── chat.ex
│ └── repo.ex
├── slackishphx.ex
├── slackishphx_web
│ ├── channels
│ ├── controllers
│ ├── endpoint.ex
│ ├── gettext.ex
│ ├── plugs
│ ├── router.ex
│ ├── templates
│ └── views
└── slackishphx_web.ex
slackishphx/lib
├── slackishphx
│ ├── application.ex
│ ├── auth
│ │ ├── auth.ex
│ │ ├── company.ex
│ │ └── user.ex
│ ├── chat
│ │ ├── channel.ex
│ │ └── chat.ex
│ └── repo.ex
├── slackishphx.ex
├── slackishphx_web
│ ├── channels
│ ├── controllers
│ ├── endpoint.ex
│ ├── gettext.ex
│ ├── plugs
│ ├── router.ex
│ ├── templates
│ └── views
└── slackishphx_web.ex
Phoenix Channels
http://phoenixframework.org/blog/the-road-to-2-million-websocket-connections
Ecto
defmodule Slackishphx.Auth.User do
use Ecto.Schema
# ...
schema "users" do
field :name, :string
field :email, :string
field :google_id, :string
field :google_token, :string
field :avatar_path, :string
belongs_to :current_company, Company
has_many :companies, Company, foreign_key: :owner_id
timestamps()
end
end
defmodule Slackishphx.Auth.User do
# ...
@doc false
def register_from_google_changeset(%User{} = user, attrs) do
user
|> cast(attrs, [:name, :email, :google_id, ...])
|> validate_required([:name, :email, ...])
end
end
defmodule Slackishphx.Auth do
@moduledoc """
The Auth context.
"""
def create_company(%User{} = user, params) do
changeset = user
|> build_assoc(:companies)
|> Company.create_company_changeset(params)
Repo.insert(changeset)
end
end
The not so good parts...
deployment
source
Comparisons*
Comparison
Laravel
(PHP) Phoenix
(Elixir)
Webapp (sessions)
API (everything needs to
go through HTTP)
Pusher
(websockets)
Worker
(broadcasting is
delivered here)
Database
(sqlite)
Queue
Webapp (sessions)
Channels (websockets)
Database (sqlite)
Some similarities
broadcast(new NewMessage(
$request->user(),
$channel,
$request->input('content'),
$request->input('uuid'),
Carbon::now()
));
broadcast!(socket, "rooms:#{room_id}:new",
%{
message: message,
user: user,
uuid: uuid,
time: DateTime.utc_now
}
)
Improvement points to Laravel
Broadcast::channel('App.User.{id}', function ($user, $id) {
return (int) $user->id === (int) $id;
});
Broadcast::channel('channels.{channel}', function (AppUser
$user, AppChannel $channel) {
if (!$channel->exists || !$user->canJoin($channel)) {
return false;
}
$user->joinChannel($channel);
return [
'id' => $user->id,
'name' => $user->name,
'avatar_path' => $user->avatar_path,
];
});
channel "companies:*", SlackishphxWeb.CompanyChannel
channel "rooms:*", SlackishphxWeb.RoomChannel
That's it!
References
● Functional Programming; What? Why? When? (Robert C Martin)
https://www.youtube.com/watch?v=7Zlp9rKHGD4
● Real World Elixir Deployment (Pete Gamache)
https://www.youtube.com/watch?v=H686MDn4Lo8
● Erlang: The Movie https://www.youtube.com/watch?v=xrIjfIjssLE
● Lonestar ElixirConf 2017- KEYNOTE: Phoenix 1.3 by Chris McCord
https://www.youtube.com/watch?v=tMO28ar0lW8
● GOTO 2016 • Phoenix a Web Framework for the New Web • José Valim
https://www.youtube.com/watch?v=bk3icU8iIto
References
● ElixirConf 2016 - Giving up the Object-Oriented Ghost by Morgan Lanco
https://www.youtube.com/watch?v=_VpZ6gQsyDY
● GOTO 2017 • Elixir: The only Sane Choice in an Insane World • Brian Cardarella
https://www.youtube.com/watch?v=gom6nEvtl3U
● Elixir, quem é esse pokemon? - Bruno Volcov
https://www.youtube.com/watch?v=aA-XHI-EYcM
● Ecto, você sabe o que é? - Amanda Sposito
https://www.youtube.com/watch?v=hQM4VdEpz6g
● Programming Phoenix (book) by Chris McCord, Bruce Tate, and José Valim
https://pragprog.com/book/phoenix/programming-phoenix

Phoenix for laravel developers

  • 1.
    Tony Messias Maceió DEVMeetup #21 Phoenix for Laravel developers
  • 3.
    This is nota hands-on talk.
  • 4.
    I'm not goingto teach you Elixir (or Phoenix) in 30~ish minutes.
  • 5.
    I'm not sayingthat one is better than the other.
  • 6.
    In fact, I'mactually showing that you can build shiny things with tools you already know.
  • 7.
    This talk ina mind-map.
  • 8.
  • 9.
  • 10.
    Haven't the Godsof programming decided that OOP has won this battle?
  • 11.
    Why is functionalprogramming in such a “hype” now?
  • 12.
    What is afunction? ● In math, functions are all about transforming input to output (sounds familiar?) ● You might be used to expressing functions in math like so: y = x * 2 or g = x ^ 2 ● But there is another notation that is much more expressive (IMO): f(x) = x * 2 f(5) = 5 * 2 f(5) = 10
  • 13.
    f(x) = x* 2 --- const f = (x) => x * 2 var x = 5 assertEquals(f(x), 10) assertEquals(f(x), f(x)) source
  • 14.
    class Value { constructor(val){ this.value = val; } } const f = (x) => x.value++ * 2 const x = new Value(5); console.log(f(x) == f(x)); // True or False? console.log(x.value); // What is the value?
  • 15.
    class Value { constructor(val){ this.value = val; } } const f = (x) => x.value++ * 2 const x = new Value(5); console.log(f(x) == f(x)); // false console.log(x.value); // 7
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
    defmodule SlackishphxWeb.CompanyController do useSlackishphxWeb, :controller plug SlackishphxWeb.Plugs.RequireAuth def create(conn, %{"company" => params}) do case Slackishphx.Auth.create_company(conn.assigns[:user], params) do {:ok, company} -> conn |> create_default_channel(company) |> switch_company(conn.assigns[:user], company) {:error, changeset} -> conn |> render("new.html", changeset: changeset) end end end
  • 23.
    defmodule SlackishphxWeb.CompanyController do useSlackishphxWeb, :controller plug SlackishphxWeb.Plugs.RequireAuth def create(conn, %{"company" => params}) do case Slackishphx.Auth.create_company(conn.assigns[:user], params) do {:ok, company} -> conn |> create_default_channel(company) |> switch_company(conn.assigns[:user], company) {:error, changeset} -> conn |> render("new.html", changeset: changeset) end end end
  • 24.
    defmodule SlackishphxWeb.CompanyController do useSlackishphxWeb, :controller plug SlackishphxWeb.Plugs.RequireAuth def create(conn, %{"company" => params}) do case Slackishphx.Auth.create_company(conn.assigns[:user], params) do {:ok, company} -> conn |> create_default_channel(company) |> switch_company(conn.assigns[:user], company) {:error, changeset} -> conn |> render("new.html", changeset: changeset) end end end
  • 25.
  • 26.
  • 27.
    Phoenix is notyour application.
  • 28.
    slackishphx/lib ├── slackishphx │ ├──application.ex │ ├── auth │ │ ├── auth.ex │ │ ├── company.ex │ │ └── user.ex │ ├── chat │ │ ├── channel.ex │ │ └── chat.ex │ └── repo.ex ├── slackishphx.ex ├── slackishphx_web │ ├── channels │ ├── controllers │ ├── endpoint.ex │ ├── gettext.ex │ ├── plugs │ ├── router.ex │ ├── templates │ └── views └── slackishphx_web.ex
  • 29.
    slackishphx/lib ├── slackishphx │ ├──application.ex │ ├── auth │ │ ├── auth.ex │ │ ├── company.ex │ │ └── user.ex │ ├── chat │ │ ├── channel.ex │ │ └── chat.ex │ └── repo.ex ├── slackishphx.ex ├── slackishphx_web │ ├── channels │ ├── controllers │ ├── endpoint.ex │ ├── gettext.ex │ ├── plugs │ ├── router.ex │ ├── templates │ └── views └── slackishphx_web.ex
  • 30.
    slackishphx/lib ├── slackishphx │ ├──application.ex │ ├── auth │ │ ├── auth.ex │ │ ├── company.ex │ │ └── user.ex │ ├── chat │ │ ├── channel.ex │ │ └── chat.ex │ └── repo.ex ├── slackishphx.ex ├── slackishphx_web │ ├── channels │ ├── controllers │ ├── endpoint.ex │ ├── gettext.ex │ ├── plugs │ ├── router.ex │ ├── templates │ └── views └── slackishphx_web.ex
  • 31.
  • 32.
  • 33.
  • 34.
    defmodule Slackishphx.Auth.User do useEcto.Schema # ... schema "users" do field :name, :string field :email, :string field :google_id, :string field :google_token, :string field :avatar_path, :string belongs_to :current_company, Company has_many :companies, Company, foreign_key: :owner_id timestamps() end end
  • 35.
    defmodule Slackishphx.Auth.User do #... @doc false def register_from_google_changeset(%User{} = user, attrs) do user |> cast(attrs, [:name, :email, :google_id, ...]) |> validate_required([:name, :email, ...]) end end
  • 36.
    defmodule Slackishphx.Auth do @moduledoc""" The Auth context. """ def create_company(%User{} = user, params) do changeset = user |> build_assoc(:companies) |> Company.create_company_changeset(params) Repo.insert(changeset) end end
  • 37.
    The not sogood parts...
  • 38.
  • 39.
  • 40.
    Comparison Laravel (PHP) Phoenix (Elixir) Webapp (sessions) API(everything needs to go through HTTP) Pusher (websockets) Worker (broadcasting is delivered here) Database (sqlite) Queue Webapp (sessions) Channels (websockets) Database (sqlite)
  • 41.
  • 42.
    Improvement points toLaravel Broadcast::channel('App.User.{id}', function ($user, $id) { return (int) $user->id === (int) $id; }); Broadcast::channel('channels.{channel}', function (AppUser $user, AppChannel $channel) { if (!$channel->exists || !$user->canJoin($channel)) { return false; } $user->joinChannel($channel); return [ 'id' => $user->id, 'name' => $user->name, 'avatar_path' => $user->avatar_path, ]; }); channel "companies:*", SlackishphxWeb.CompanyChannel channel "rooms:*", SlackishphxWeb.RoomChannel
  • 43.
  • 44.
    References ● Functional Programming;What? Why? When? (Robert C Martin) https://www.youtube.com/watch?v=7Zlp9rKHGD4 ● Real World Elixir Deployment (Pete Gamache) https://www.youtube.com/watch?v=H686MDn4Lo8 ● Erlang: The Movie https://www.youtube.com/watch?v=xrIjfIjssLE ● Lonestar ElixirConf 2017- KEYNOTE: Phoenix 1.3 by Chris McCord https://www.youtube.com/watch?v=tMO28ar0lW8 ● GOTO 2016 • Phoenix a Web Framework for the New Web • José Valim https://www.youtube.com/watch?v=bk3icU8iIto
  • 45.
    References ● ElixirConf 2016- Giving up the Object-Oriented Ghost by Morgan Lanco https://www.youtube.com/watch?v=_VpZ6gQsyDY ● GOTO 2017 • Elixir: The only Sane Choice in an Insane World • Brian Cardarella https://www.youtube.com/watch?v=gom6nEvtl3U ● Elixir, quem é esse pokemon? - Bruno Volcov https://www.youtube.com/watch?v=aA-XHI-EYcM ● Ecto, você sabe o que é? - Amanda Sposito https://www.youtube.com/watch?v=hQM4VdEpz6g ● Programming Phoenix (book) by Chris McCord, Bruce Tate, and José Valim https://pragprog.com/book/phoenix/programming-phoenix