GETTING REAL

WITH ELIXIR

& PHOENIX FRAMEWORK

ME

  • Tawsif Aqib
  • FullStack Polyglot Developer
  • Engineering Manager
  • Pathao

We'll Cover

  • Elixir
  • WebSocket
  • Phoenix Framework
  • Realtime App
  • Code Example

Elixir

PROGRAMMING LANGUAGE

Elixir is a dynamic, functional language designed for building scalable and maintainable applications.

Ruby

Jose Valim

Erlang

Elixir

  • Created on 1986
  • Ericsson Computer Science Lab
  • Open Telecom Platform (OTP)
  • Open sourced on 1996
  • Fault-tolerant
  • Concurrent
  • Distributed
  • Functional

Elixir Features

  • Functional
  • Pattern Matching
  • Pipe Operator
  • Immutable Data
  • Actor Model
  • OTP
  • Let it Crash

USING ELIXIR/ERLANG

  • Amazon
  • Yahoo!
  • Pinterest
  • Facebook
  • Heroku
  • Whatsapp
  • RabbitMQ
  • League of Legends

 

Code Sample

WRITE SOME ELIXIR

WEBSOCKET

REALTIME WEB

Traditional WEb

Request

Response

Browser/Client

Server

RealTime WEb

Connect

Data/Payload

Browser/Client

Server

WebSocket

  • TCP
  • Full-duplex
  • Event/PubSub
  • Example
    • Google Map
    • Uber
    • FourSquare

Phoenix Framework

MVC WEB APPLICATION FRAMEWORK

Why phoenix?

  • Fast Response Time (Microsecond)
  • Scalable
  • Fault Talerant
  • Maintainable
  • Concurrent
  • MVC
  • No Magic

Controller

defmodule MvcExampleWeb.UserController do
  use MvcExampleWeb, :controller

  alias MvcExample.Accounts
  alias MvcExample.Accounts.User

  def index(conn, _params) do
    users = Accounts.list_users()
    render(conn, "index.html", users: users)
  end

  def new(conn, _params) do
    changeset = Accounts.change_user(%User{})
    render(conn, "new.html", changeset: changeset)
  end
end

Model

defmodule MvcExample.Accounts.User do
  use Ecto.Schema
  import Ecto.Changeset
  alias MvcExample.Accounts.User


  schema "users" do
    field :age, :integer
    field :name, :string

    timestamps()
  end

  @doc false
  def changeset(%User{} = user, attrs) do
    user
    |> cast(attrs, [:name, :age])
    |> validate_required([:name, :age])
  end
end

View

<%= form_for @changeset, @action, fn f -> %>
  <div class="form-group">
    <%= label f, :name, class: "control-label" %>
    <%= text_input f, :name, class: "form-control" %>
    <%= error_tag f, :name %>
  </div>

  <div class="form-group">
    <%= label f, :age, class: "control-label" %>
    <%= number_input f, :age, class: "form-control" %>
    <%= error_tag f, :age %>
  </div>

  <div class="form-group">
    <%= submit "Submit", class: "btn btn-primary" %>
  </div>
<% end %>

Router

defmodule MvcExampleWeb.Router do
  use MvcExampleWeb, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

  scope "/", MvcExampleWeb do
    pipe_through :browser # Use the default browser stack

    get "/", PageController, :index

    # Users resoruceful endpoints
    resources "/users", UserController
  end
end

Migration

defmodule MvcExample.Repo.Migrations.CreateUsers do
  use Ecto.Migration

  def change do
    create table(:users) do
      add :name, :string
      add :age, :integer

      timestamps()
    end

  end
end

Generator

# Generates a Phoenix channel
mix phx.gen.channel

# Generates a context with functions around an Ecto schema
mix phx.gen.context    

# Generates an embedded Ecto schema file
mix phx.gen.embedded   

# Generates controller, views, and context for an HTML resource
mix phx.gen.html       

# Generates controller, views, and context for a JSON resource
mix phx.gen.json       

# Generates a Presence tracker
mix phx.gen.presence   

# Generates an Ecto schema and migration file
mix phx.gen.schema 

Test

defmodule MvcExample.AccountsTest do
  use MvcExample.DataCase

  alias MvcExample.Accounts

  describe "users" do
    alias MvcExample.Accounts.User

    test "list_users/0 returns all users" do
      {:ok, user} =
        attrs
        |> Enum.into(%{age: 42, name: "some name"})
        |> Accounts.create_user()

      assert Accounts.list_users() == [user]
    end
  end
end

Channel

defmodule Chat.RoomChannel do
  use Phoenix.Channel

  def join("rooms:lobby", message, socket) do
    send(self, {:after_join, message})
    {:ok, socket}
  end

  def handle_in("new:msg", msg, socket) do
    broadcast! socket, "new:msg", %{user: msg["user"], body: msg["body"]}
    {:reply, {:ok, %{msg: msg["body"]}}, assign(socket, :user, msg["user"])}
  end

  def handle_info({:after_join, msg}, socket) do
    broadcast! socket, "user:entered", %{user: msg["user"]}
    push socket, "join", %{status: "connected"}
    {:noreply, socket}
  end
end

More

  • GenServer
  • Supervisor
  • Process
  • Agent
  • Task
  • Macro
  • Node

REALTIME ELiXIR APP

WRITE THE CODE

WRITE COPY/PASTE THE CODE

BUILDING Chat App

IN  10 MINUTES

New Phoenix App

mix phx.new chat --no-ecto

cd chat

cd assets

yarn

cd ..

mix phx.server

Prepare the View

# lib/chat_web/templates/page/index.html.eex

<!-- The list of messages will appear here: -->
<ul id='msg-list' class='row' style='list-style: none; min-height:200px; padding: 10px;'></ul>

<div class="row">
  <div class="col-xs-3">
    <input type="text" id="name" class="form-control" placeholder="Your Name" autofocus>
  </div>
  <div class="col-xs-9">
    <input type="text" id="msg" class="form-control" placeholder="Your Message">
  </div>
</div>

Create the channel

mix phx.gen.channel Room
# lib/chat_web/channels/user_socket.ex

defmodule ChatWeb.UserSocket do
  use Phoenix.Socket

  ## Channels
  channel "room:lobby", ChatWeb.RoomChannel

  # ...

end

Add the JavaScript

# assets/js/app.js

import socket from "./socket"

// connect to chat "room"
var channel = socket.channel('room:lobby', {});

// listen to the 'shout' event
channel.on('shout', function (payload) {
  var li = document.createElement("li");
  var name = payload.name || 'guest';
  li.innerHTML = '<b>' + name + '</b>: ' + payload.message;
  ul.appendChild(li);
});

// join the channel.
channel.join();

var ul = document.getElementById('msg-list');
var name = document.getElementById('name');
var msg = document.getElementById('msg');

// "listen" for the [Enter] keypress event to send a message:
msg.addEventListener('keypress', function (event) {
  if (event.keyCode == 13 && msg.value.length > 0) {
    channel.push('shout', {
      name: name.value,
      message: msg.value
    });
    msg.value = '';
  }
});

did it work?

Elixir for the future

Slide Link

QUESTION

FROM THE AUDIENCE

Thanks

FOR YOUR ATTENTION

Made with Slides.com