2017-05-29 50 views
1

我見過Elixir GenServer的幾個例子,但他們主要處理數組值(例如購物車)或計數器增量。因此他們演示瞭如何處理簡單的數據類型。如何在Phoenix/Elixir Genserver中傳遞模型

我想知道如何在更新某些模型記錄時在Phoenix應用程序中傳遞狀態。

示例我可以提供的是:

  • 步驟1:我接收AWS SNS通知(包含數據的溶液中加入什麼新S3對象)=>只是存儲消息來建模Notification
  • 步驟2:我解析Notification中的消息以讀取s3對象filename。那麼這個存儲新Document模型
  • 第三步:我取回S3對象(例如ORIGINAL_NAME)的元數據,並存儲

紅寶石即將on Rails的我會做到這一點,因爲這:

  • 控制器創建Notification然後第二步驟時間表後臺作業(Sidekiq)
  • 背景作業創建文件和時間表高就拉元數據PullDocumentMetadata.perform_later(「文件」,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(一步一步調用)

第一步鳳凰類似的事情(創建Notification由鳳凰做控制器,我想隔離其他兩個步驟到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(ProcessNotification,notification)來初始化GenServer,那麼Genserver會如何保存Struct的狀態...所以它會對對象進行單元化,還是這個反物質?
  • 我怎麼才能從演員身上投下,所以從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 

我敢肯定,我在做什麼是錯的,所以我很感謝任何想法,這應該怎麼更好。

回答

2

演員陣容很簡單。

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

但是,我甚至不知道爲什麼你需要這樣做,因爲你好像從你的控制器啓動了gen server。我不認爲這是正確的做法。你在這裏似乎需要的是產生一個過程來完成所有的工作。

在這裏,您可以使用任務模塊

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

如果你想處理錯誤處理和重試,那麼你可以使用上級啓動任務。

如果您擔心您將創建的進程數量,那麼您會查看工作池。

順便說一句,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處理程序。

+0

Re:'但GenServer.cast是異步的,所以它不是一個問題.'這是我擔心的事情,如果一個異步可以投另一個異步和teoretically投出另一個異步,如果我改變任何那些GenServer的價值主管會確保正確的執行順序,從「{Notification,123}」到「{Document,234}」的步驟? ......聽起來,任何'handle_cast'方法都應該被調用/包含一次,並且在那個執行gen server中只應該使用'handle_info'。 (感覺對我來說'handle_cast - > handle_info - > handle_info'類似於公共方法,調用一堆私有方法) – equivalent8

+0

...並感謝您的建議,真的很有幫助。我可以看到我看錯了:)我將不得不再次訪問GenServer上的章節。 – equivalent8

相關問題