Phoenix

elixir

Vad?

  • Framework ovanpå Elixir
  • Ruby on Rails på räls
    • Explicit istället för implicit
    • Concurrency first
    • Funktionellt
├── _build
├── assets
├── config
├── deps
├── lib
│   └── cannelloni <- domain / business logic
│   └── cannelloni_web.ex <- webserver logic
├── priv
└── test

Hur ser det ut?

├── _build
├── assets
├── config
├── deps
├── lib
│   └── cannelloni <- domain / business logic
│   └── cannelloni_web.ex <- webserver logic
├── priv
└── test

Hur ser det ut?

lib/cannelloni_web
├── channels
│   └── user_socket.ex
├── controllers
│   └── page_controller.ex
├── templates
│   ├── layout
│   │   └── app.html.eex
│   └── page
│       └── index.html.eex
├── views
│   ├── error_helpers.ex
│   ├── error_view.ex
│   ├── layout_view.ex
│   └── page_view.ex
├── endpoint.ex
├── gettext.ex
├── router.ex
└── telemetry.ex

Hur ser det ut?

lib/cannelloni_web
├── channels
│   
├── controllers
│   
├── live
│   └── recipe_live   
│
│
├── templates      
│   
│   
├── views
│   
│   
│   
│   
├── endpoint.ex
├── gettext.ex
├── router.ex
└── telemetry.ex
defmodule CannelloniWeb.Router do
  use CannelloniWeb, :router
  ...
  
  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_live_flash
    plug :fetch_current_user
    plug :fetch_username
    plug :put_root_layout, {CannelloniWeb.LayoutView, :root}
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end
  
  scope "/", CannelloniWeb do
    pipe_through [:browser, :redirect_if_user_is_authenticated]

    get "/users/register", UserRegistrationController, :new
    post "/users/register", UserRegistrationController, :create
    get "/users/log_in", UserSessionController, :new
    post "/users/log_in", UserSessionController, :create
  end
  
  scope "/", CannelloniWeb do
    pipe_through [:browser, :require_authenticated_user]

    live "/recipes", RecipeLive.Index
    live "/recipes/new", RecipeLive.New
  end
end  
defmodule CannelloniWeb.UserRegistrationController do
  use CannelloniWeb, :controller

  alias Cannelloni.Accounts
  alias Cannelloni.Accounts.User
  alias CannelloniWeb.UserAuth

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

  def create(conn, %{"user" => user_params}) do
    case Accounts.register_user(user_params) do
      {:ok, user} ->
        {:ok, _} =
          Accounts.deliver_user_confirmation_instructions(
            user,
            &Routes.user_confirmation_url(conn, :confirm, &1)
          )

        conn
        |> put_flash(:info, "User created successfully.")
        |> UserAuth.log_in_user(user)

      {:error, %Ecto.Changeset{} = changeset} ->
        render(conn, "new.html", changeset: changeset)
    end
  end
end

Varför ska vi använda det?

  • Alla connections får en egen process
  • Skalbarhet per default
  • Realtid per default
    • lätt att kommunicera mellan processer
  • Hit the ground running
  • Elixir (och Erlang) är vettiga byggstenar

Live view

Vad?

  • Ett tillägg till Phoenix för att göra vyer dynamiska
  • Använder websockets
  • Fortfarande i beta (v0.15.0)

Hur?

  • Skickar endast små diffar till servern
  • Alla klienter har en egen process, kan inte bli chokead

Varför?

  • Slipper Javascript
  • Vyerna är deklarativt skrivna
  • Realtids-synk med backend
  • Statiskt renderad "first meaningful paint" (till skillnad från JS-sidor)
  • Bra för dåliga anslutningar (dåligt för offline-support)
...


<ul id="ingredient-list">
  <%= for ingredient <- Enum.reverse(@recipe.ingredients) do %>
    <li class="ingredient-list-item">
      <%= ingredient %>
    </li>
  <% end %>
  <%= if @current_user do %>
    <li  id="add-ingredient-input">
      <input id="add-ingredient" type="text" phx-key="Enter" phx-window-keydown="add_ingredient" phx-hook="Input" 
      	placeholder="T.ex. krossade tomater"/>
    </li>
  <% end %>
</ul>

show.html.leex

...

  def mount(_params, session, socket) do
    {:ok, assign(socket, :current_user, session["current_user"])}
  end


  def handle_params(%{"id" => id}, _, socket) do
    {:noreply, assign(socket, :recipe, RecipeList.get_recipe!(id))}
  end


  def handle_event("add_ingredient", %{"value" => ingredient}, socket) do
    {:ok, recipe} = RecipeList.add_ingredient(socket.assigns.recipe, ingredient)

    socket =
      socket
      |> assign(:recipe, recipe)
      |> push_event("clear_input", %{})

    {:noreply, socket}
  end
end

show.ex

Live view

Vad?

  • Ett tillägg till Phoenix för att göra vyer dynamiska
  • Använder websockets
  • Fortfarande i beta (v0.15.0)

Hur?

  • Skickar endast små diffar till servern
  • Alla klienter har en egen process, kan inte bli chokead

Varför?

  • Slipper Javascript
  • Vyerna är deklarativt skrivna
  • Realtids-synk med backend
  • Statiskt renderad "first meaningful paint" (till skillnad från JS-sidor)
  • Bra för dåliga anslutningar (dåligt för offline-support)

Channels (PubSub)

  • Realtidskommunicering mellan processer
  • Fungerar mellan olika noder i ett kluster

Presence

Testing

  • Inbyggda testing moduler
  • Väldigt snabbt (krävs ingen browser)
  • Kan dock inte testa javascript

Tack

deck

By Mikael Gråborg

deck

  • 114