Hello! I’m Sophie I’m an engineer and sometimes teacher at The Flatiron School where I build education and business tools in Elixir and Ruby. @sm_debenedetto 3 *and this is my dog
And... I’m Michael I’m a lead engineer at RentPath, where we build websites to help people find a place to live. @michaelstalker 4 *and this is my family
Elixir School A free, open-source Elixir curriculum Through the hard work of many volunteers around the world, we’ve developed and translated content covering everything from intro topics to deep dives. 5
Get Involved! Help us grow the Elixir School community Write a lesson, contribute a TIL (or longer!) blog post or provide a translation. Open an issue here describing your idea or simply open a PR with your work to get started. 6
What We’ll Build A real-time ticket estimation tool ○ Users can see other users in the “estimation room” in real-time ○ Users can vote on tickets ○ Users can see the winning vote tallied in real-time and move on to the next ticket 9
User Starts Estimation Round When I click “start pointing!” Then the first card for estimation appears for all users in the room Features Real-Time User Presence When a new user joins/leaves Then I see the list of present users update in the UI 12
Ticket is Estimated When another user submits a vote Then I see the UI update to indicate they have voted Features Winner is Calculated When the last user submits a vote Then all users see the winning vote And a user can click “next card” 13
Application Starting State Log In with a username (We built some dummy authorization flows) See the static ticket estimation page Click the start button and... Oh no you can’t estimate a ticket! 14
What is the Real-Time Web? Users receive new information from the server as soon as it is available—no request required This represents a significant departure from traditional HTTP communication Made possible by WebSockets 16
What are WebSockets? A Protocol built on top of TCP that allows for bi-directional, "full-duplex" communication between the client and the server by creating a persistent connection between the two. 18
22 We’ll use WebSockets to maintain a persistent, bi-directional and stateful connection between our ticket estimation clients and the server with the help of Phoenix LiveView
What is LiveView? Phoenix LiveView leverages server-rendered HTML and Phoenix’s native WebSocket tooling so you can build fancy real-time features without all that complicated JavaScript. 24
Connecting ● Client connects to Phoenix server over WebSockets, at the LiveView socket endpoint ● Phoenix server starts a LiveView process for that user’s socket 29
Handling Changes ● Certain user interactions on the page will send a message over the socket to the LiveView process ● The LiveView process will update its state and push a message back down the socket ● The leex template will re-render with the new state 31
Connection Process ● User visits ”/cards” in the browser ● CardController mounts the LiveView ● The LiveView renders the LEEX template as static HTML 51
Connection Process ● User visits ”/cards” in the browser ● CardController mounts the LiveView ● The LiveView renders the LEEX template as static HTML ● A JS snippet on that static HTML page sends the “WebSocket connect” request 52
Connection Process ● User visits ”/cards” in the browser ● CardController mounts the LiveView ● The LiveView renders the LEEX template as static HTML ● A JS snippet on that static HTML page sends the “WebSocket connect” request ● The LiveView process re-renders the template over WebSockets, is now listening for events from the client 53
54 LiveView Client Request GET /cards Render LEEX Template Start pointing! Step 1. Mount the LiveView + render static HTML CardController Mount LiveView
61 # app/pointing_party_web/live/card_live.ex defmodule PointingPartyWeb.CardLive do use Phoenix.LiveView alias PointingPartyWeb.CardView def render(assigns) do Phoenix.View.render(CardView, "index.html", assigns) end def mount(_session, socket) do {:ok, assign(socket, is_pointing: false)} end end
67 defmodule PointingPartyWeb.CardController do use PointingPartyWeb, :controller import Phoenix.LiveView.Controller def index(conn, _params) do %{assigns: %{username: username}} = conn live_render(conn, PointingPartyWeb.CardLive, session: %{username: username}) end end
User initiates pointing round 92 Define the LiveView ● Define a LiveView and LEEX template - We did this for you :) ● Render the LiveView from the controller - We did this for you :) Handle Events ● Add a phx-click event to the “start pointing” button ● Define a handle_event/3 function to match this event and update socket.assigns accordingly Configure LiveView ● We did this for you :)
What is PubSub? PubSub (“publish/subscribe”) is a pattern in which we publish messages to a “topic”, such that those messages can be consumed by any number of subscribers. 99
What is Phoenix PubSub? The Phoenix PubSub library allows us subscribe Elixir processes to a shared topic and publish messages to those processes. Phoenix Channels abstract away the interactions with Phoenix PubSub, but you can also use the PubSub library directly, which is exactly what we’ll do from within our LiveView. 100
Phoenix PubSub and Distributed Elixir Phoenix PubSub is configured by default to be distribution-friendly with Phoenix.PubSub.PG2. With this adapter, messages are broadcast to processes sharing a topic across nodes. 101
What is Phoenix Presence? The Phoenix.Presence module allows us to: ● Register a process under a given topic ● Store that info in a decentralized and resilient data store. ● Broadcast presence-related events and sync presence data with ease. 116
Phoenix Presence Uses a CRDT A CRDT (Conflict-free Replicated Data Type) backs Phoenix Presence. What’s so special about a CFRDT? Unlike centralized data stores like Redis, Phoenix Presence… 118
“ …gives you high availability and performance because we don't have to send all data through a central server. It also gives you resilience to failure because the system automatically recovers from failures. - Chris McCord 119
Broadcast Presence to Existing Users ● Teach LiveView to handle the presence broadcast by fetching the list of present users from the presence data store 123
Lists of Maps of Lists of Integers StreamData.list_of( StreamData.map_of( StreamData.atom(:alphanumeric), StreamData.list_of(StreamData.integer()) ) ) 153
Possible Test Implementation property "number of row elements is n+1" do check all number <- StreamData.integer(), row_index = abs(number) do element_count = length(Pascal.row(row_index)) assert element_count == row_index + 1 end end 159
Stuff that’s true of any solution ○ Each row is symmetrical ○ The first and last elements of each row are 1 ○ After row 0, the second element in each list is the row index 170 1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 …
Stuff that’s true of any solution ○ Each row is symmetrical ○ The first and last elements of each row are 1 ○ After row 0, the second element in each list is the row index ○ So is the second-to-last element 171 1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 …
Stuff that’s true of any solution ○ We can represent any row as a list ○ Row 0 just has one element: 1 ○ For all rows after 0, 1 appears twice 174 1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 …
Stuff that’s true of any solution ○ We can represent any row as a list ○ Row 0 just has one element: 1 ○ For all rows after 0, 1 appears twice ○ Each row n has n+1 elements in it 175 1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 …
Stuff that’s true of any solution ○ We can represent any row as a list ○ Row 0 just has one element: 1 ○ For all rows after 0, 1 appears twice ○ Each row n has n+1 elements in it ○ In each row, numbers go up, then down 176 1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 …
Voting Results def calculate_votes(users) do case winning_vote(users) do top_two when is_list(top_two) -> {"tie", top_two} winner -> {"winner", winner} end end
A Property Test property "calculated vote is a list or an integer" do check all users <- user_generator, {_event, winner} = calculate_votes(users) # We'll assert something here later end end
What’s true of any solution? def calculate_votes(users) do case winning_vote(users) do top_two when is_list(top_two) -> {"tie", top_two} winner -> {"winner", winner} end end
Resources ○ “An introduction to property-based testing” by Scott Wlaschin ○ “Choosing properties for property-based testing” by Scott Wlaschin ○ Property-Based Testing with PropEr, Erlang, and Elixir and PropEr Testing by Fred Hebert ○ SteamData documentation ○ “Testing the Hard Stuff and Staying Sane” by John Hughes ○ “Picking Properties to Test in Property Based Testing” by Michael Stalker 187