如何在Phoenix / Elixir Genserver中传递模型

时间:2017-05-29 13:54:50

标签: elixir phoenix-framework gen-server

我已经看过Elixir GenServer的几个例子,但它们主要处理数组值(例如购物车)或计数器增量。因此,他们演示了如何处理简单的数据类型。

我想知道在更新某些模型记录时如何在Phoenix应用程序中传递状态。

我可以提供的例子是:

  • step1:我收到AWS SNS通知(包含添加了新s3对象的数据)=>只需将消息存储到模型Notification
  • step2:我解析Notification内的消息,读取s3对象filename。然后将其存储到新的Document模型
  • step3:我获取s3对象的元数据(例如original_name)并存储它

来自 Ruby on Rails 我会这样做:

  • 控制器创建Notification然后安排第二步的后台作业(Sidekiq)
  • 后台作业创建文档并安排另一个作业来拉取元数据PullDocumentMetadata.perform_later(“Document”,document.id)

示例:

 class NotificationController
   def create
     # ...
     notification = Notification.create(body: message_body)
     ProcessNotification.perform_later("Notification", notification.id)
     # ...
   end
 end

 class ProcessNotification
   # ...
   def process(resource_class, resource_id)
     notification = resource_class.constantize.find(resource_id)
     document = Document.new(filename: parse_filename(notification.body))
     document.save
     PullMetadata.perform_later("Document", document.id)
   end
   # ...
 end

 class PullMetadata
   # ...
   def process(resource_class, resource_id)
     document = resource_class.constantize.find(resource_id)
     document.original_name = fetch_original_name(document.filename)
     document.save
   end
   # ...
 end

现在我试图使用Genserver 复制与Phoenix类似的内容(逐步调用)

第一步(创建Notification由Phoenix控制器完成,我想将其他两个步骤隔离为2个genserver调用:

defmodule NotificationController do
   # ...
   def create(conn, params) do
     notification = # ... store body to %{}Notification
     # ...
     pid = GenServer.start_link(ProcessNotification, {Notification, notification.id})
     GenServer.cast(pid, :process_to_document)
   end
end

defmodule ProcessNotification do
   def handle_cast(:process_to_document, {Notification, notification_id}) do
     notification = Repo.get(Notification, notification_id)
     filename = not_important_how_i_parse_body(notification)

     doc = %{}Document |> Document.changeset(%{filename: filename}) |> Repo.insert!

     {:noreply, {Document, document.id}}
   end

   def handle_cast(:pull_metadata, {Document, document_id}) do
     document = Repo.get(Document, document_id)
     original_name = not_important_how_i_pull_the_metadata(document)

     doc = %{}Document |> Document.changeset(%{original_name: original_name}) |> Repo.update!

     {:noreply, {Document, document.id}}
   end     
end

现在我的问题是:

  • 我正在改变Genserver的状态(最初它是{Notification, id},然后它是{Document, id}。感觉像Genserver一直期待可能是同一类型?所以也许我应该总是返回`{Notification,id}并从关联中拉出Document?或者这样可以吗?
  • 如果我使用`pid = GenServer.start_link(进程通知,通知)来初始化GenServer,Genserver将保持结构状态的效果......那么它会对该对象进行备用,还是这个反物质?
  • 我如何实际从演员表中投射,所以process_to_document我会投射pull_metadata。或者我应该在控制器中安排这些:

示例:

defmodule NotificationController do
   # ...
   def create(conn, params) do
     notification = # ... store body to %{}Notification
     # ...
     pid = GenServer.start_link(ProcessNotification, {Notification, notification.id})
     GenServer.cast(pid, :process_to_document)
     GenServer.cast(pid, :pull_metadata)

   end
end

我很确定我所做的事情是错的,所以我很欣赏这应该如何做得更好。

1 个答案:

答案 0 :(得分:2)

演员阵容很简单。

GenServer.cast self(), {:another_event, some_data}

但是,我甚至不确定为什么你需要这样做,因为你似乎是从你的控制器启动gen服务器。我不认为这是正确的做法。你现在需要的就是产生一个完成所有工作的过程。

您可以在此处使用任务模块

Task.start fn -> 
  # do my heady lifting here
end

如果您想处理错误处理和重试,那么您可以使用主管来启动任务。

如果您担心要创建的进程数,那么您将查看工作池。

BTW,GenServer状态非常简单。初始状态是从init / 1返回的状态。下一个状态是您从handle_cast / 2或handle_call / 3

返回的内容
defmodule MyGenServer do
  use GenServer

  def start_link(args) do
    init_args = # do something with args
    GenServer.start_link(__MODULE__, init_args)
  end

  def init(init_args) do
    initial_state = # perhaps manipulate init_args
    {:ok, initial_state}
  end

  def handle_cast(event, current_state) do
    new_state = # manipulate current_state
    GenServer.cast self(), {:another_event, some_data}
    {:noreply, new_state}
  end

  def handle_call(event, sender, current_date) do
    new_state = # manipulate current_state
    {:reply, return_value, new_state}
  end
end

请记住,每个handle_xxx都需要在另一个handle_xxx被调用之前运行完成。因此,您无法将GenServer.call调用到同一个GenServer,因为这会使进程死锁。

但是GenServer.cast是异步的所以它不是问题。

另一个选项是从GenServer处理程序中执行send self(), {:event, data}。这将在GenServer上运行handle_info/2处理程序。