Feeding the Fire – Anatomy of an Endpoint



Feeding the Fire – Anatomy of an Endpoint

0 0


PhoenixPresentation

Feeding the Fire with Phoenix - presentation given at Boulder Elixir meetup

On Github boulder-elixir / PhoenixPresentation

Feeding the Fire

Concurrent |> Fast |> Reliable

Created by Josh Bavari / @jbavari

Why Elixir / Phoenix?

  • Concurrent - run thousands of processes
  • Fast - response times in Microseconds (500 µs)
  • Reliable - recover from errors

Concurrent

  • Background tasks built in
  • Send messages to other processes
  • No need for Resque or DelayedJobs!
  • Easy to send messages to processes

Fast

  • Responses typically in microseconds
  • Capable of serving two million connections on single node
  • Router compiles to cat-quick pattern matching
  • Templates are precompiled
  • Comparision chart

Reliable

  • Supervision of processes baked in
  • Recovery is a first class citizen
  • Powerful tools to debug & monitor

Quick Elixir Review

  • Functions are defined by their arity
  • `create/1` or `create/2`
  • Pattern matching
  • `{result, list} = MyModule.doApiCall(token)`
  • Pipe operator - `|>`
  • ingredients |> stir |> bake |> eat

The players of the game

  • Elixir = functional language, compiles to BEAM
  • Phoenix = web framework for backend / frontend
  • Mix = build tool for Elixir
  • Hex = Elixir's package manager
  • Ecto = database tool

Out of the box..

  • Live reloading on code changes
  • Websockets set up
  • Asset compilation/minification of JS / CSS / IMG
  • Database connectivity, migration, retrieval
  • Automatic error recovery

Getting started is easy

mix local.hex
mix archive.install https://github.com/phoenixframework/archives/raw/master/phoenix_new.ez
mix phoenix.new hello_phoenix
          

Looking inside Phoenix

Phoenix makes explicit calls with pipes

lters data along the way, and sends response

connection
|> phoenix

=> Response
          

Peeling the onion back further

connection
|> endpoint
|> router
|> pipelines
|> controller
|> action
|> view
|> template
          

Introducing Plugs

  • Plugs are functions
  • Your web applications are pipelines of plugs
  • Plugs take a connection, modify, returns new conn

Anatomy of an Endpoint

  • Handles all aspects of requests until Router
  • Provides core set of plugs to all requests
  • Dispatches requests into designated router

Anatomy of a Router

  • Parses incoming requests and dispatches to controller/action
  • Provides helpers for route paths or urls
  • Defines named pipelines for requests
  • Pipelines allow grouping of plugs

Anatomy of a Controller

  • Provides actions to handle requests
  • Prepare data and pass into views
  • Invoke rendering via views
  • Perform redirects

Anatomy of a View

  • Render templates
  • Act as presentation layer
  • Define helper functions available in templates

Anatomy of a Template

  • HTML template as they sound
  • Are precompiled

Anatomy of a Channel

  • Manage socket for realtime communication
  • Analagous to Controllers
  • Allow bi-directional communication
  • Persistent connections

Plug - the core of it all

A look at an endpoint

defmodule MyApp.Endpoint do
  use Phoenix.Endpoint, otp_app: :MyApp

  socket "/socket", MyApp.UserSocket

  # Serve at "/" the static files from "priv/static" directory.
  #
  # You should set gzip to true if you are running phoenix.digest
  # when deploying your static files in production.
  plug Plug.Static,
    at: "/", from: :MyApp, gzip: false,
    only: ~w(css fonts images js favicon.ico robots.txt)

  # Code reloading can be explicitly enabled under the
  # :code_reloader configuration of your endpoint.
  if code_reloading? do
    socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
    plug Phoenix.LiveReloader
    plug Phoenix.CodeReloader
  end

  plug Plug.RequestId
  plug Plug.Logger

  plug Plug.Parsers,
    parsers: [:urlencoded, :multipart, :json],
    pass: ["*/*"],
    json_decoder: Poison

  plug Plug.MethodOverride
  plug Plug.Head

  plug Plug.Session,
    store: :cookie,
    key: "_MyApp_key",
    signing_salt: "PD+nvtps"

  plug MyApp.Router
end

          

Plug - the core of it all

A look at a router

defmodule MyApp.Router do
  use MyApp.Web, :router

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

  pipeline :api do
    plug :accepts, ["json"]
  end

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

    resources "/user", UsersController
    resources "/songs", SongController
    get "/", PageController, :index
  end

  # Other scopes may use custom stacks.
  # scope "/api", MyApp do
  #   pipe_through :api
  # end
end

          

Sockets are dead simple

# From channel itself:
broadcast! socket, "new:msg", %{user: msg["user"], body: msg["body"]}

# From IEx / Controller
Chat.Endpoint.broadcast("rooms:lobby", "new:msg", %{message: "hello from the console"})
          

Database access -> Ecto

  • Provides Repo for database
  • Models define data schema and assocations
  • Queries tie models and repositories
  • Changeset declare transformations to models
  • Changesets do type casting, validations, and more
  • Adapter to database of choice (PostgreSQL, MySQL, etc)
  • Additional clients for Redis, Riak, RethinkDB, etc

Static Assets

  • Comes with default HTML5 build tool, Brunch
  • Easy to swap out for Webpack or browserify
  • Live reloading on changes
  • Run when server runs
  • Support for ES6
  • SASS, image compression, and more

OTP

  • Provides framework for general servers
  • Supervise processes, restart when error occurs
  • Finite state machine
  • Agents to hold state in separate process
  • Distributed database server (Mnesia)
  • Event handling
  • And so much more...

Supervision of processes

defmodule MyApp do
  use Application

  # See http://elixir-lang.org/docs/stable/elixir/Application.html
  # for more information on OTP Applications
  def start(_type, _args) do
    import Supervisor.Spec, warn: false

    children = [
      # Start the endpoint when the application starts
      supervisor(MyApp.Endpoint, []),
      # Start the Ecto repository
      worker(MyApp.Repo, []),
      # Here you could define other workers and supervisors as children
      worker(MyApp.BackgroundWorker, [arg1, arg2, arg3]),
      worker(MyApp.Cleanup, [arg1, arg2, arg3]),
      worker(MyApp.Emailer, [arg1, arg2, arg3]),
    ]

    # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
    # for other strategies and supported options
    opts = [strategy: :one_for_one, name: MyApp.Supervisor]
    Supervisor.start_link(children, opts)
  end

  # Tell Phoenix to update the endpoint configuration
  # whenever the application is updated.
  def config_change(changed, _new, removed) do
    MyApp.Endpoint.config_change(changed, removed)
    :ok
  end
end
          

Powerful monitoring

  • Try out `:observer.start()`
  • Instantly see processes, memory usage
  • View supervision of application
  • Metrics of when to split out supervision
  • Normally runs at 15MB usage!

Develop fast with the REPL

  • Write and test code instantly
  • Run `iex -S mix` to load your application
  • Run `recompile` and hot-reload code to try out
  • You can even `pry` debug points, test code in place

It's ahead of it's time.

Many of the things you need to add into Node / Rails / Etc has been in Erlang for 20+ years. State machines, process watching / restarting, pattern matching, and stateful processes are available now!

Want more?

  • Tons of packages you could hope for
  • Hex is growing very quickly
  • 1,672 packages (as of 3/6/2016)
  • Authentication - controller, socket, and more
  • Alternate data access => Moebius

Let's look at code!

We'll take a peek at the Phoenix chat example

Some great resources

Feeding the Fire Concurrent |> Fast |> Reliable Created by Josh Bavari / @jbavari