Skip to content

Commit

Permalink
Create new talk for HR mini conf 2024
Browse files Browse the repository at this point in the history
  • Loading branch information
vnegrisolo committed Oct 9, 2024
1 parent b8f346d commit 93685b0
Show file tree
Hide file tree
Showing 15 changed files with 365 additions and 24 deletions.
2 changes: 1 addition & 1 deletion .tool-versions
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ruby 3.1.2
ruby 3.3.3
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

source 'https://rubygems.org'

ruby '3.1.2'
ruby '3.3.3'

gem 'jekyll'
gem 'jekyll-sitemap'
Expand Down
48 changes: 28 additions & 20 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
GEM
remote: https://rubygems.org/
specs:
addressable (2.8.1)
public_suffix (>= 2.0.2, < 6.0)
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
bigdecimal (3.1.8)
colorator (1.1.0)
concurrent-ruby (1.1.10)
concurrent-ruby (1.3.4)
em-websocket (0.5.3)
eventmachine (>= 0.12.9)
http_parser.rb (~> 0)
eventmachine (1.2.7)
ffi (1.15.5)
ffi (1.17.0)
forwardable-extended (2.6.0)
google-protobuf (4.27.3)
bigdecimal
rake (>= 13)
http_parser.rb (0.8.0)
i18n (1.12.0)
i18n (1.14.5)
concurrent-ruby (~> 1.0)
jekyll (4.3.0)
jekyll (4.3.3)
addressable (~> 2.4)
colorator (~> 1.0)
em-websocket (~> 0.5)
Expand All @@ -30,8 +34,8 @@ GEM
safe_yaml (~> 1.0)
terminal-table (>= 1.8, < 4.0)
webrick (~> 1.7)
jekyll-sass-converter (2.2.0)
sassc (> 2.0.1, < 3.0)
jekyll-sass-converter (3.0.0)
sass-embedded (~> 1.54)
jekyll-sitemap (1.4.0)
jekyll (>= 3.7, < 5.0)
jekyll-watch (2.2.1)
Expand All @@ -40,26 +44,30 @@ GEM
rexml
kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0)
liquid (4.0.3)
listen (3.7.1)
liquid (4.0.4)
listen (3.9.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
mercenary (0.4.0)
pathutil (0.16.2)
forwardable-extended (~> 2.6)
public_suffix (5.0.0)
public_suffix (6.0.1)
rake (13.2.1)
rb-fsevent (0.11.2)
rb-inotify (0.10.1)
rb-inotify (0.11.1)
ffi (~> 1.0)
rexml (3.2.5)
rouge (4.0.0)
rexml (3.3.5)
strscan
rouge (4.3.0)
safe_yaml (1.0.5)
sassc (2.4.0)
ffi (~> 1.9)
sass-embedded (1.77.8)
google-protobuf (~> 4.26)
rake (>= 13)
strscan (3.1.0)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
unicode-display_width (2.3.0)
webrick (1.7.0)
unicode-display_width (2.5.0)
webrick (1.8.1)

PLATFORMS
ruby
Expand All @@ -70,7 +78,7 @@ DEPENDENCIES
webrick (~> 1.7)

RUBY VERSION
ruby 3.1.2p20
ruby 3.3.3p89

BUNDLED WITH
2.2.16
2.5.11
4 changes: 2 additions & 2 deletions _data/social.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ twitter:
name: '@vfnegrisolo'
icon: twitter
linkedin:
link: https://www.linkedin.com/in/vinicius-ferreira-negrisolo-53146a38
name: Vinicius Ferreira Negrisolo
link: https://www.linkedin.com/in/vnegrisolo
name: vnegrisolo
icon: linkedin
258 changes: 258 additions & 0 deletions _posts/talks/2024-08-27-phoenix-liveview-gen-server.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
---
layout: talk
title: "Phoenix LiveView & GenServer"
categories: Talks
tags: Elixir Phoenix
date: 2024-08-27 12:00:00
last_modified_at: 2024-08-27 12:00:00
---

My Goal is to talk about how GenServer defines Web Development for LiveView

---

# But before let's talk about Ruby

```ruby
class SyncData < ApplicationSubscriber
attach_to %w[address phone]
attr_reader :model

def created(model)
@model = model

case model
when Address
sync_address
when Phone
sync_phone
end
end

def sync_address
body = model.slice(:address1, :city, :state, :zip)
API.post(url: "/address/#{model.user_id}", body:)
end

def sync_phone
body = model.slice(:number)
API.post(url: "/phone/#{model.user_id}", body:)
end
end
```

---

# Is concurrency a problem in Elixir?

- messages between processes
- no threads with shared memory
- data immutability
- function with args
- GenServer with state
- ETS/PG and other data sources

---

# What is a GenServer 1/2

> A GenServer is a process like any other Elixir process and it can be used to keep state, execute code asynchronously and so on.
- ****asynchronously**
- built in **mailbox** for incoming messages
- one message at a time
- builds and keep data in memory
- other processes can interact with

---

# What is a GenServer 2/2

![genserver]

---

# Phoenix LiveView Basics

- uses Phoenix Controller for HTTP GET
- uses Phoenix Channel for WS
- 2 calls once on `mount`, `live_params` and `render`

---

# Phoenix LiveView HTTP vs WS

![live-view-http]

---

# Phoenix LiveView Client Events

![live-view-client-events]

---

# Phoenix LiveView PubSub

![live-view-pubsub]

---

# LiveView & GenServer

- LiveView uses Phoenix Channel
- Phoenix Channel uses GenServer
- WS connection has their own state

| characteristics | implications |
| --- | --- |
| in memory state can go away | the **deploy** problem |
| state growth increases server memory | **too much data** problem |
| user can get locked on long queries | **1 message at a time** problem |

---

# How it feels

| How it feels | How's it Supposed to Feel |
| --- | --- |
| ![obstacle] | ![obstacle]{: .rotate-90 } |

---

# The deploy problem

- find a less volitile way to store the user state:
- query params (open modal)
- form inputs (leverage form recovery after 1st connection)
- LocalStorage

```elixir
# router
scope "/families", FamilyLive do
live("/", Index, :table)
live("/board", Index, :board)
live("/new", Index, :new)
end
```

---

# Too much data problem

- LiveView Streams => temporary assigns
- `stream(socket, :songs, [song1, song2, song3])`

```elixir
def mount(_params, _session, socket) do
stream(socket, :songs, ["song1", "song2", "song3"]) |> ok()
end

def handle_event("add_something", %{"song" => song}, socket) do
socket |> stream_insert(:songs, song, at: -1) |> noreply()
end

def render(assigns) do
~H"""
<ul id="songs" phx-update="stream">
<li :for=\{\{dom_id, song} <- @streams.songs} id={dom_id}>
<%= song %>
</li>
</ul>
"""
end
```

---

# One message at a time problem

- `assign_async` / `start_async`

```elixir
def mount(%{"slug" => slug}, _, socket) do
socket |> assign_async(:org, fn -> {:ok, %{org: fetch_org!(slug)}} end) |> ok()
end

def render(assigns) do
~H"""
<div :if={@org.loading}>Loading organization...</div>
<div :if={@org.ok? && @org.result}><%= @org.result.name %> loaded!</div>
"""
end
```

---

# Phoenix LiveView Async

![live-view-async]

---

# Async Stream idea

> if the data takes too long to be retrieved and if it's too much to keep in memory
- mix stream & async

```elixir
defmodule MyAppWeb.Admin.UserLive.Index do
use MyAppWeb, :live_view

def mount(_params, _session, socket) do
socket
|> stream(:users, [])
|> assign_users()
|> ok()
end

def handle_async(:users, {:ok, results}, socket) do
socket
|> assign(:users, AsyncResult.ok(:users))
|> stream(:users, results, reset: true)
|> noreply()
end

def render(assigns) do
~H"""
<div class={["mt-6 flex flex-col gap-2", @users.loading && "loading"]}>
<.table id="users" rows={@streams.users}>
<:col :let={ {_id, user} } label="Role"><%= user.role %></:col>
<:col :let={ {_id, user} } label="Email"><%= user.email %></:col>
</.table>
</div>
"""
end

def assign_users(socket) do
%{current_user: current_user} = socket.assigns

socket
|> assign(:users, AsyncResult.loading())
|> start_async(:users, fn -> Repo.search(User, current_user, %{}) end)
end
end
```

---

# Summary

- LV has all building blocks ready
- Major benefits:
- scales well
- fresh data for free
- easiest to refactor
- no graphql/json/xml layers
- js is no law land so kept to minimal
- LiveView is the Best tool for the job

{% include markdown/acronyms.md %}

[genserver]: /images/genserver.png 'GenServer'
[live-view-http]: /images/live-view-http.png 'LiveView HTTP'
[live-view-client-events]: /images/live-view-client-events.png 'LiveView Client Events'
[live-view-pubsub]: /images/live-view-pubsub.png 'LiveView PubSub'
[live-view-async]: /images/live-view-async.png 'LiveView Async'
[obstacle]: /images/obstacle.png 'obstacle'
4 changes: 4 additions & 0 deletions _sass/base.scss
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,7 @@ blockquote {
width: 100%;
}
}

.rotate-90 {
transform: rotate(90deg);
}
Binary file added images/genserver.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 93685b0

Please sign in to comment.