回調模塊
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
, update
和delete
操做後, 會回自產生這些事件. 經過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
一個事件能夠設置多個訂閱者回調函數
多個模型的事件處理邏輯能夠放在一個位置
生命週期事件在單獨的進程中處理, 不會阻塞模型的執行流
增長了複雜度
有可能過分封裝, 代碼不直觀