I’ve read interesting topic where I found practical example of how to use new Registry. Based on mentioned topic I’ve created Supervisors and Genservers, but I’m not sure if this implementation is correct for my needs or if I’m missing something. I want to create Room
by room id than under it spawn Groups and many Users, Room should store all users and groups ids to fast check if user or group exists. Also I want to let user join and leave at once many groups or get all groups that user belongs to. In code below I got stuck in all functions with require IEx;IEx.pry
, e.g. :get_user_groups
how can I get user groups from children processes? Can I use Registry to subscribe users to their groups or in current implementation can I just extract this data by e.g. Registry.lookup?
Supervisors:
defmodule MyApp.Room.SystemsSupervisor do
use Supervisor
def start_link() do
IO.puts "Starting MyApp.Room.SystemsSupervisor"
Supervisor.start_link(
__MODULE__,
nil
)
end
def init(_) do
children = [
supervisor(Registry, [:unique, Registry.ProcessRegistry], id: :proc),
supervisor(MyApp.Room.RoomSupervisor, [])
]
supervise(children, strategy: :rest_for_one)
end
end
defmodule MyApp.Room.RoomSupervisor do
use Supervisor
def start_link() do
IO.puts "Starting MyApp.Room.RoomSupervisor"
Supervisor.start_link(
__MODULE__,
nil,
name: :rooms_supervisor
)
end
def start_child(room_id, user) do
Supervisor.start_child(
:rooms_supervisor,
[room_id, user]
)
end
def init(_) do
children = [
supervisor(MyApp.Room.SingleRoomSupervisor, []),
]
supervise(children, strategy: :simple_one_for_one)
end
end
defmodule MyApp.Room.SingleRoomSupervisor do
use Supervisor
def start_link(room_id, user) do
IO.puts "Starting SingleRoomSupervisor"
Supervisor.start_link(
__MODULE__,
{room_id, user},
name: via_tuple(room_id)
)
end
defp via_tuple(room_id) do
{:via, Registry, {Registry.ProcessRegistry, {:room_sup, room_id}}}
end
def init({room_id, user}) do
children = [
supervisor(MyApp.Room.UserSupervisor, [room_id]),
supervisor(MyApp.Room.GroupSupervisor, [room_id]),
worker(MyApp.Room.Room, [room_id, user]),
]
supervise(children, strategy: :one_for_all)
end
end
defmodule MyApp.Room.GroupSupervisor do
use Supervisor
def start_link(room_id) do
IO.puts "Starting MyApp.Room.GroupSupervisor"
Supervisor.start_link(
__MODULE__,
nil,
name: via_tuple(room_id)
)
end
defp via_tuple(room_id) do
{:via, Registry, {Registry.ProcessRegistry, {:group_sup, room_id}}}
end
def start_child(room_id, group_id, user_id) do
Supervisor.start_child(
via_tuple(room_id), #user_id
[group_id, user_id]
)
end
def init(_) do
children = [
worker(MyApp.Room.Group, [])
]
supervise(children, strategy: :simple_one_for_one)
end
end
defmodule MyApp.Room.UserSupervisor do
use Supervisor
def start_link(room_id) do
IO.puts "Starting MyApp.Room.UserSupervisor"
Supervisor.start_link(
__MODULE__,
nil,
name: via_tuple(room_id)
)
end
defp via_tuple(room_id) do
{:via, Registry, {Registry.ProcessRegistry, {:user_sup, room_id}}}
end
def start_child(room_id, user) do
Supervisor.start_child(
via_tuple(room_id),
[user.id, user.username]
)
end
def init(_) do
children = [
worker(MyApp.Room.User, [])
]
supervise(children, strategy: :simple_one_for_one)
end
end
Genservers:
defmodule MyApp.Room.Room do
use GenServer
defstruct groups: [], users: [], room_id: :none
# Client API
def start_link(room_id, user) do
IO.puts "Starting Room for #{room_id}"
GenServer.start_link(
__MODULE__,
{room_id, user},
name: via_tuple(room_id)
)
end
defp via_tuple(room_id) do
{:via, Registry, {Registry.ProcessRegistry, {:room, room_id}}}
end
def new(room_id, user) do
# r = Registry.lookup(Registry.ProcessRegistry, {:room, room_id})
# g = GenServer.whereis(via_tuple(room_id))
# IO.inspect {g, "vs", r}
case GenServer.whereis(via_tuple(room_id)) do
nil ->
MyApp.Room.RoomSupervisor.start_child(room_id, user)
room_pid ->
send(room_pid, {:real_init, room_id, user})
end
end
def get_user_groups(room_id, user_id) do
GenServer.call(via_tuple(room_id), {:get_user_groups, user_id}, 50 * 10000)
end
def user_joined_groups(room_id, user_id, groups) do
GenServer.call(via_tuple(room_id), {:user_joined_groups, user_id, groups}, 50 * 10000)
end
def user_left_groups(room_id, user_id, groups) do
GenServer.call(via_tuple(room_id), {:user_left_groups, user_id, groups}, 50 * 10000)
end
# Server Callbacks
def init({room_id, user}) do
send(self(), {:real_init, room_id, user})
{:ok, %__MODULE__{room_id: room_id}}
end
def handle_call({:get_user_groups, user_id}, _from, state) do
require IEx;IEx.pry
{:noreply, state}
end
def handle_call({:user_joined_groups, user_id, groups}, _from, state) do
require IEx;IEx.pry
{:noreply, state}
end
def handle_call({:user_left_groups, user_id, groups}, _from, state) do
require IEx;IEx.pry
{:noreply, state}
end
def handle_info({:real_init, room_id, user}, state) do
areas = prepare_areas(room_id, user)
{:ok, _user} = MyApp.Room.UserSupervisor.start_child(room_id, user)
{:noreply, %__MODULE__{state | areas: state.areas ++ areas, users: state.users ++ [user.id]}}
end
# Helper Functions
defp prepare_areas(room_id, user) do
for group <- user.groups do
MyApp.Room.Group.new(room_id, group, user.id)
group.id
end
end
end
defmodule MyApp.Room.Group do
use GenServer
defstruct users: [], group_id: :none
# Client API
def start_link(group_id, user_id) do
IO.puts "Starting group: #{group_id}"
GenServer.start_link(
__MODULE__,
[group_id, user_id],
name: via_tuple(group_id)
)
end
defp via_tuple(group_id) do
{:via, Registry, {Registry.ProcessRegistry, {:group, group_id}}}
end
def new(room_id, group_id, user_id) do
case GenServer.whereis(via_tuple(group_id)) do
nil ->
IO.inspect {"creating group #{group_id} with user: #{user_id}"}
MyApp.Room.GroupSupervisor.start_child(room_id, group_id, user_id)
group_pid ->
IO.inspect {"updating group #{group_id} with user: #{user_id}"}
send(group_pid, {:add_user, user_id})
end
end
# Server Callbacks
def init([group_id, user_id]) do
{:ok, %__MODULE__{group_id: group_id, users: [user_id]}}
end
def handle_info({:add_user, user_id}, state) do
{:noreply, %__MODULE__{state | users: state.users ++ [user_id]}}
end
end
defmodule MyApp.Room.User do
use GenServer
defstruct username: nil, id: :none, metas: []
# Client API
def start_link(user_id, username) do
IO.puts "Starting user: #{user_id}"
GenServer.start_link(
__MODULE__,
[user_id, username],
name: via_tuple(user_id)
)
end
defp via_tuple(user_id) do
{:via, Registry, {Registry.ProcessRegistry, {:user, user_id}}}
end
# Server Callbacks
def init([user_id, username]) do
{:ok, %__MODULE__{id: user_id, username: username}}
end
end