Date post: | 16-Apr-2017 |
Category: |
Technology |
Upload: | preston-marshall |
View: | 1,673 times |
Download: | 0 times |
OTP, RethinkDB, and Phoenix Channels
My Background
• Rails-rider since 2008
• Used Erlang/OTP via RabbitMQ, ejabberd, etc for a long time
• Erlang/OTP never really "clicked"
• Accent is from Athens, Alabama, by way of Birmingham
Elixir
• Nicer Syntax
• Excellent build tools
• Macros (`use` especially)
• OTP wrappers
OTP
• Open Telecom Platform
• Humongous
• Most commonly used libraries/behaviours
• supervisors
• gen_server
• gen_event
• gen_fsm
Supervisors
• Generally formed as trees
• Many restart strategies and knobs
• Supervises "workers"
• gen_servers
• Tasks
• other supervisors!
Money Trees
When the system keeps working through multiple partial failures
Credit ThisOTPLife.tumblr.com
GenServer
• Generic Server
• It's a behaviour
• Implement the callbacks
• Elixir provides a nice wrapper
Naive Server
defmodule KeyValueStore do def start do ServerProcess.start(KeyValueStore) end
def put(pid, key, value) do ServerProcess.call(pid, {:put, key, value}) end
def get(pid, key) do ServerProcess.call(pid, {:get, key}) end
def init do HashDict.new end
def handle_call({:put, key, value}, state) do {:ok, HashDict.put(state, key, value)} end
def handle_call({:get, key}, state) do {HashDict.get(state, key), state} end end
Sample code from Elixir in Action by
Saša Jurić
Naive Serverdefmodule ServerProcess do def start(callback_module) do spawn(fn -> initial_state = callback_module.init loop(callback_module, initial_state) end) end
defp loop(callback_module, current_state) do receive do {request, caller} -> {response, new_state} = callback_module.handle_call( request, current_state )
send(caller, {:response, response}) loop(callback_module, new_state) end end
def call(server_pid, request) do send(server_pid, {request, self})
receive do {:response, response} -> response end end end
Sample code from Elixir in Action by
Saša Jurić
Improved with GenServerdefmodule KeyValueStore do use GenServer
def start do GenServer.start(KeyValueStore, nil) end
def put(pid, key, value) do GenServer.cast(pid, {:put, key, value}) end
def get(pid, key) do GenServer.call(pid, {:get, key}) end
def init(_) do {:ok, HashDict.new} end
def handle_cast({:put, key, value}, state) do {:noreply, HashDict.put(state, key, value)} end
def handle_call({:get, key}, _, state) do {:reply, HashDict.get(state, key), state} end end
Sample code from Elixir in Action by
Saša Jurić
• Quickly becoming "Rails for Elixir,"
• Simpler and easy to understand
• Routing, Templating, and Models
• Channels!
Phoenix Channels
• Another behaviour
• Quite a bit like GenServers
• Bi-directional controllers
• Persistent connection with the client (browser, mobile, IoT)
RethinkDB
• JSON database from the ground up
• LINQ-like queries
• Scaling, Sharding, Replication across datacenters
• Changefeeds!
Changefeeds
• Real-time stream of changes
• Subscribe to table, document, or an arbitrary query
• Aggregation support in the works
• Changefeeds and Phoenix channels work great together
Tweets Changefeed Query
import RethinkDB.Query require RethinkDB.Lambda import RethinkDB.Lambda db("elixirfriends") |> table("tweets") |> changes |> ElixirFriends.Database.run |> Stream.take_every(1) |> Enum.each fn(change) -> %{"new_val" => post} = change html = Phoenix.View.render_to_string ElixirFriends.PostView, "_post.html", post: post push socket, "tweet", %{view: html} end
Javascript
import {Socket} from "phoenix" let socket = new Socket("/ws") socket.connect() let chan = socket.chan("tweets") chan.join().receive("ok", chan => { console.log(`Subscribed to tweets`) }) chan.on("tweet", function(msg) { console.log(msg) $('#tweets').prepend(msg.view); }) let App = {} export default App
Live Demo
Conclusion
• OTP provides the building blocks
• Elixir makes them easy to use
• Phoenix is awesome
• Phoenix Channels and RethinkDB changefeeds make a supremely unique pair