您如何在 Elixir 中的进程之间共享状态?

时间:2021-02-17 20:29:18

标签: design-patterns elixir phoenix-framework software-design

我的目标是使用 Phoenix 创建一个遵循最佳实践的简单聊天应用程序。为了共谋,该应用程序是围绕聊天“房间”构建的,其中每个房间都包含一个消息列表。每个房间都有一个关联的标识符(一个六个字符长的字母数字字符串),以便其他用户可以输入并加入。

目前,我将其建模为两个模块:一个用于处理用户输入并显示房间中消息列表的 LiveView,以及一个用于跟踪房间状态的模块。代码大致是这样的

defmodule Chat.ChatServer do
  use GenServer

  # Client

  def start_link(room_id) do
    GenServer.start_link(__MODULE__, %{room_id: room_id, messages: []}, name: via_tuple(room_id))
  end

  def whereis(room_id) do
    GenServer.whereis(via_tuple(room_id))
  end

  defp via_tuple(room_id) do
    {:via, Registry, {Registry.Chat, room_id}}
  end

  def add_message(room_id, message) do
    GenServer.cast(via_tuple(room_id), {:add_message, message})
  end

  def get_messages(room_id) do
    GenServer.call(via_tuple(room_id), :get_messages)
  end

  def subscribe(room_id) do
    Phoenix.PubSub.subscribe(Chat.PubSub, "chat:#{room_id}")
  end

  # Server

  def init(messages) do
    {:ok, messages}
  end

  def handle_cast({:add_message, new_message}, %{messages: messages} = state) do
    new_messages = [new_message | messages]
    {:noreply, %{state | messages: new_messages}, {:continue, :notify_subscribers}}
  end

  def handle_call(:get_messages, _from, %{messages: messages} = state) do
    {:reply, messages, state}
  end

  def handle_continue(:notify_subscribers, %{room_id: room_id} = state) do
    Phoenix.PubSub.broadcast(Chat.PubSub, "chat:#{room_id}", {:updated_messages})
    {:noreply, state}
  end
end
defmodule ChatWeb.ChatLive do
  use ChatWeb, :live_view

  alias Chat.{ChatServer}

  def mount(%{"id" => room_id}, _session, socket) do
    if connected?(socket) do
      ChatServer.subscribe(room_id)
    end

    assigns = [
      messages: ChatServer.get_messages(room_id),
      room_id: room_id
    ]

    {:ok, assign(socket, assigns)}
  end

  def handle_event("add_message", _, %{assigns: %{room_id: room_id, messages: messages}} = socket) do
    ChatServer.add_message(room_id, "Example message")
    {:noreply, socket}
  end

  def handle_info({:updated_messages}, %{assigns: %{room_id: room_id}} = socket) do
    messages = ChatServer.get_messages(room_id)
    {:noreply, assign(socket, :messages, messages)}
  end
end

如您所见,我已将聊天室建模为 GenServer。这样做而不是将所有房间保留在单个 GenServer 进程中是否有任何缺点?

这是正确使用 GenServer 吗?使用一个 GenServer 来包装所有房间,而不是每个房间一个 GenServer 进程是不是更好的方法?

我观察到但不知道如何解决的一些问题是,截至目前,每个进程都将自身的 name 保持在其状态。唯一的原因是它只能向订阅该特定房间的进程广播。这是我对观察者模式的尝试。是否有一种简单的方法可以在不保持 name 状态的情况下执行此操作?

最后,避免 LiveView 在新消息附加到状态之前从 GenServer 重新获取消息。这是对 handle_continue/2 的滥用吗?

0 个答案:

没有答案
相关问题