Elixir Ecto: 事件流處理和回調

回調模塊 Ecto.Model.Callbacks 已經在 2.0 中被廢棄, 1.x 版本的 Ecto 能夠繼續使用.數據庫

Ecto 提供了 before_insert, after_insert這樣的回調函數來在數據庫操做先後作一些事情, 咱們常常把這種函數稱爲鉤子. 咱們這裏有一個例子, 用戶註冊完成後須要向其郵箱發送一封激活郵件. 一般咱們在控制器(UserController)中實現一個create函數用於建立用戶, 例如:設計模式

def create(conn, %{"user" => params}) do
  changeset = User.changeset(%User{}, params)
  case Repo.insert(changeset) do
    {:ok, user} ->
      send_activation_email(user)
    {:error, changeset} ->
      Logger.error "Can not register user"
  end
end

注意這段代碼可能在整個系統中有不少重複的, 由於可能會在不少地方須要用到用戶註冊的代碼, 好比Web入口, RESTFUL API接口, 等等. 所以, send_activation_email(user)必須在每一個註冊用戶的地方調用這種相同功能的代碼函數

實現一個事件廣播程序(事件管理器), 咱們在須要產生事件的地方能夠調用EctoTest.EventManager.broadcast/1函數來發送一個事件給事件管理器EctoTest.EventManager. 它會把事件發送給各個已註冊的處理器, 若是該事件是某個處理器須要處理的, 就進入事件處理程序, 若是不是, 事件處理程序會忽略該事件:設計

defmodule EctoTest.EventManager do
  @handlers [
    # 在這裏插入事件處理器模塊
  ]
  def start_link do
    {:ok, manager} = GenEvent.start_link(name: __MODULE__)
    Enum.each(@handlers, &GenEvent.add_handler(manager, &1, []))
    {:ok, manager}
  end
  def broadcast(event) do
    GenEvent.notify(__MODULE__, event)
  end
end

上述事件管理器實際上在軟件工程當中實現了一個發佈/訂閱設計模式, EctoTest.EventManager是一個事件發佈者, @handlers屬性是一堆訂閱者, 當有事件產生時, EctoTest.EventManager會把這個事件廣播出去.code

注意, 須要把EctoTest.EventManager掛載到監控樹下面接口

children = [
  worker(EctoTest.EventManager, [])
]

廣播生命週期事件

一個模型的生命週期實際上就是一堆事件, 好比insert, update, 和delete, 下面咱們來建立一個這樣的生命週期模塊:生命週期

defmodule EctoTest.ModelLifecycle do
  # Ecto.Model.Callbacks 在 2.0 已經被廢棄
  import Ecto.Model.Callbacks
  alias EctoTest.EventManager
  defmacro __using__(_) do
    quote do
      import unquote(__MODULE__)
      after_insert :broadcast_event, [:insert]
      after_update :broadcast_event, [:update]
      after_delete :broadcast_event, [:delete]
    end
  end
  # 以以下的格式廣播事件
  # {:model, :update, changeset}
  def broadcast_event(changeset, type) do
    EventManager.broadcast({:model, type, changeset})
    changeset
  end
end

而後在模型當中使用這個生命週期模塊:進程

defmodule EctoTest.Model.User do
  use Ecto.Model
  use EctoTest.ModelLifecyle
  # ...
end

如今,當模型在執行insert, updatedelete操做後, 會回自產生這些事件. 經過GenEvent API, 咱們在能夠收到這些通知(事件)時作一些事情. 如今咱們來完成上述用戶註冊後的郵件發送功能, 建立一個EctoTest.EmailSender, 它實現了 GenEvent 行爲:事件

defmodule EctoTest.EmailSender do
  use GenEvent
  alias EctoTest.Model.User
  def handle_event({:model, :insert, %{model: %User{}} = changeset}, state) do
    send_activation_email(changeset.model)
    {:ok, state}
  end
end

好處

  • 一個事件能夠設置多個訂閱者回調函數

  • 多個模型的事件處理邏輯能夠放在一個位置

  • 生命週期事件在單獨的進程中處理, 不會阻塞模型的執行流

缺點

  • 增長了複雜度

  • 有可能過分封裝, 代碼不直觀

相關文章
相關標籤/搜索