回调模块
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
一个事件能够设置多个订阅者回调函数
多个模型的事件处理逻辑能够放在一个位置
生命周期事件在单独的进程中处理, 不会阻塞模型的执行流
增长了复杂度
有可能过分封装, 代码不直观